OLD | NEW |
| 1 import * as dartStyle from 'dart-style'; |
1 import * as ts from 'typescript'; | 2 import * as ts from 'typescript'; |
2 import {Transpiler} from './main'; | 3 |
| 4 import {OutputContext, Transpiler} from './main'; |
3 | 5 |
4 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; | 6 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; |
| 7 export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableD
eclaration | |
| 8 ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration; |
| 9 |
| 10 export type Set = { |
| 11 [s: string]: boolean |
| 12 }; |
5 | 13 |
6 export function ident(n: ts.Node): string { | 14 export function ident(n: ts.Node): string { |
7 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; | 15 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; |
8 if (n.kind === ts.SyntaxKind.QualifiedName) { | 16 if (n.kind === ts.SyntaxKind.QualifiedName) { |
9 let qname = (<ts.QualifiedName>n); | 17 let qname = (<ts.QualifiedName>n); |
10 let leftName = ident(qname.left); | 18 let leftName = ident(qname.left); |
11 if (leftName) return leftName + '.' + ident(qname.right); | 19 if (leftName) return leftName + '.' + ident(qname.right); |
12 } | 20 } |
13 return null; | 21 return null; |
14 } | 22 } |
15 | 23 |
| 24 export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration):
boolean { |
| 25 return ifDecl.members && ifDecl.members.length === 1 && |
| 26 ifDecl.members[0].kind === ts.SyntaxKind.CallSignature; |
| 27 } |
| 28 |
| 29 export function getDeclaration(type: ts.Type): ts.Declaration { |
| 30 let symbol = type.getSymbol(); |
| 31 if (!symbol) return null; |
| 32 if (symbol.valueDeclaration) return symbol.valueDeclaration; |
| 33 return symbol.declarations && symbol.declarations.length > 0 ? symbol.declarat
ions[0] : null; |
| 34 } |
| 35 |
| 36 export function isExtendsClause(heritageClause: ts.HeritageClause) { |
| 37 return heritageClause.token === ts.SyntaxKind.ExtendsKeyword && |
| 38 heritageClause.parent.kind !== ts.SyntaxKind.InterfaceDeclaration; |
| 39 } |
| 40 export function isConstructor(n: ts.Node): boolean { |
| 41 return n.kind === ts.SyntaxKind.Constructor || n.kind === ts.SyntaxKind.Constr
uctSignature; |
| 42 } |
| 43 |
| 44 export function isStatic(n: ts.Node): boolean { |
| 45 let hasStatic = false; |
| 46 ts.forEachChild(n, (child) => { |
| 47 if (child.kind === ts.SyntaxKind.StaticKeyword) { |
| 48 hasStatic = true; |
| 49 } |
| 50 }); |
| 51 return hasStatic; |
| 52 } |
| 53 |
| 54 export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { |
| 55 if (isFunctionType(type, tc)) return true; |
| 56 if (type.kind === ts.SyntaxKind.TypeReference) { |
| 57 if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call
).length > 0) |
| 58 return true; |
| 59 } |
| 60 return false; |
| 61 } |
| 62 |
| 63 export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { |
| 64 let kind = type.kind; |
| 65 if (kind === ts.SyntaxKind.FunctionType) return true; |
| 66 if (kind === ts.SyntaxKind.TypeReference) { |
| 67 let t = tc.getTypeAtLocation(type); |
| 68 if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; |
| 69 } |
| 70 |
| 71 if (kind === ts.SyntaxKind.UnionType) { |
| 72 let types = (<ts.UnionTypeNode>type).types; |
| 73 for (let i = 0; i < types.length; ++i) { |
| 74 if (!isFunctionType(types[i], tc)) { |
| 75 return false; |
| 76 } |
| 77 } |
| 78 return true; |
| 79 } |
| 80 // Warning: if the kind is a reference type and the reference is to an |
| 81 // interface that only has a call member we will not return that it is a |
| 82 // function type. |
| 83 if (kind === ts.SyntaxKind.TypeLiteral) { |
| 84 let members = (<ts.TypeLiteralNode>type).members; |
| 85 for (let i = 0; i < members.length; ++i) { |
| 86 if (members[i].kind !== ts.SyntaxKind.CallSignature) { |
| 87 return false; |
| 88 } |
| 89 } |
| 90 return true; |
| 91 } |
| 92 return false; |
| 93 } |
| 94 |
| 95 export function isTypeNode(node: ts.Node): boolean { |
| 96 switch (node.kind) { |
| 97 case ts.SyntaxKind.UnionType: |
| 98 case ts.SyntaxKind.TypeReference: |
| 99 case ts.SyntaxKind.TypeLiteral: |
| 100 case ts.SyntaxKind.LastTypeNode: |
| 101 case ts.SyntaxKind.ArrayType: |
| 102 case ts.SyntaxKind.TypePredicate: |
| 103 case ts.SyntaxKind.TypeQuery: |
| 104 case ts.SyntaxKind.TupleType: |
| 105 case ts.SyntaxKind.NumberKeyword: |
| 106 case ts.SyntaxKind.StringKeyword: |
| 107 case ts.SyntaxKind.VoidKeyword: |
| 108 case ts.SyntaxKind.BooleanKeyword: |
| 109 case ts.SyntaxKind.AnyKeyword: |
| 110 case ts.SyntaxKind.FunctionType: |
| 111 return true; |
| 112 default: |
| 113 return false; |
| 114 } |
| 115 } |
| 116 |
| 117 export function isCallable(decl: ClassLike): boolean { |
| 118 let members = decl.members as Array<ts.ClassElement>; |
| 119 return members.some((member) => { return member.kind === ts.SyntaxKind.CallSig
nature; }); |
| 120 } |
| 121 |
| 122 export function copyLocation(src: ts.TextRange, dest: ts.TextRange) { |
| 123 dest.pos = src.pos; |
| 124 dest.end = src.end; |
| 125 } |
| 126 |
| 127 // Polyfill for ES6 Array.find. |
| 128 export function arrayFindPolyfill<T>( |
| 129 nodeArray: ts.NodeArray<T>, predicate: (node: T) => boolean): T { |
| 130 for (let i = 0; i < nodeArray.length; ++i) { |
| 131 if (predicate(nodeArray[i])) return nodeArray[i]; |
| 132 } |
| 133 return null; |
| 134 } |
| 135 |
| 136 export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { |
| 137 for (let parent = n; parent; parent = parent.parent) { |
| 138 if (parent.kind === kind) return parent; |
| 139 } |
| 140 return null; |
| 141 } |
| 142 |
| 143 export function getEnclosingClass(n: ts.Node): ClassLike { |
| 144 for (let parent = n.parent; parent; parent = parent.parent) { |
| 145 if (parent.kind === ts.SyntaxKind.ClassDeclaration || |
| 146 parent.kind === ts.SyntaxKind.InterfaceDeclaration) { |
| 147 return <ClassLike>parent; |
| 148 } |
| 149 } |
| 150 return null; |
| 151 } |
| 152 |
| 153 export function isConstCall(node: ts.CallExpression): boolean { |
| 154 return node && ident(node.expression) === 'CONST_EXPR'; |
| 155 } |
| 156 |
| 157 export function isInsideConstExpr(node: ts.Node): boolean { |
| 158 return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpr
ession)); |
| 159 } |
| 160 |
| 161 export function formatType(s: string, comment: string, insideCodeComment: boolea
n): string { |
| 162 if (!comment) { |
| 163 return s; |
| 164 } else if (insideCodeComment) { |
| 165 // When inside a comment we only need to emit the comment version which |
| 166 // is the syntax we would like to use if Dart supported all language |
| 167 // features we would like to use for interop. |
| 168 return comment; |
| 169 } else { |
| 170 let sb = s + '/*'; |
| 171 // Check if the comment is a valid type name in which case it is safe to use
the Dart code |
| 172 // written in comments syntax. |
| 173 const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;'; |
| 174 comment = comment.trim(); |
| 175 let statement = comment + stubToMakeTypeValidStatement; |
| 176 let result = dartStyle.formatCode(statement); |
| 177 |
| 178 if (!result.error) { |
| 179 result.code = result.code.trim(); |
| 180 let expectedStubIndex = result.code.length - stubToMakeTypeValidStatement.
length; |
| 181 if (result.code.lastIndexOf(stubToMakeTypeValidStatement) === expectedStub
Index) { |
| 182 comment = result.code.substring(0, expectedStubIndex).trim(); |
| 183 sb += '='; |
| 184 } |
| 185 } |
| 186 sb += comment; |
| 187 sb += '*/'; |
| 188 return sb; |
| 189 } |
| 190 } |
| 191 |
16 export class TranspilerBase { | 192 export class TranspilerBase { |
17 private idCounter: number = 0; | 193 private idCounter: number = 0; |
18 constructor(private transpiler: Transpiler) {} | 194 constructor(protected transpiler: Transpiler) {} |
19 | 195 |
20 visit(n: ts.Node) { this.transpiler.visit(n); } | 196 visit(n: ts.Node) { this.transpiler.visit(n); } |
| 197 pushContext(context: OutputContext) { this.transpiler.pushContext(context); } |
| 198 popContext() { this.transpiler.popContext(); } |
21 emit(s: string) { this.transpiler.emit(s); } | 199 emit(s: string) { this.transpiler.emit(s); } |
22 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); } | 200 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); } |
| 201 emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); } |
| 202 maybeLineBreak() { return this.transpiler.maybeLineBreak(); } |
| 203 enterCodeComment() { return this.transpiler.enterCodeComment(); } |
| 204 exitCodeComment() { return this.transpiler.exitCodeComment(); } |
| 205 get insideCodeComment() { return this.transpiler.insideCodeComment; } |
| 206 |
| 207 emitImport(toEmit: string) { |
| 208 if (!this.transpiler.importsEmitted[toEmit]) { |
| 209 this.pushContext(OutputContext.Import); |
| 210 this.emit(`import "${toEmit}";`); |
| 211 this.transpiler.importsEmitted[toEmit] = true; |
| 212 this.popContext(); |
| 213 } |
| 214 } |
| 215 |
23 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess
age); } | 216 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess
age); } |
24 | 217 |
25 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } | 218 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } |
26 | 219 |
27 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); } | 220 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); } |
28 | 221 |
29 visitEachIfPresent(nodes?: ts.Node[]) { | 222 visitEachIfPresent(nodes?: ts.Node[]) { |
30 if (nodes) this.visitEach(nodes); | 223 if (nodes) this.visitEach(nodes); |
31 } | 224 } |
32 | 225 |
33 visitList(nodes: ts.Node[], separator = ',') { | 226 visitList(nodes: ts.Node[], separator = ',') { |
34 for (let i = 0; i < nodes.length; i++) { | 227 for (let i = 0; i < nodes.length; i++) { |
35 this.visit(nodes[i]); | 228 this.visit(nodes[i]); |
36 if (i < nodes.length - 1) this.emit(separator); | 229 if (i < nodes.length - 1) this.emitNoSpace(separator); |
37 } | 230 } |
38 } | 231 } |
39 | 232 |
40 uniqueId(name: string): string { | 233 uniqueId(name: string): string { |
41 const id = this.idCounter++; | 234 const id = this.idCounter++; |
42 return `_${name}\$\$ts2dart\$${id}`; | 235 return `_${name}\$\$js_facade_gen\$${id}`; |
43 } | 236 } |
44 | 237 |
45 assert(c: ts.Node, condition: boolean, reason: string): void { | 238 assert(c: ts.Node, condition: boolean, reason: string): void { |
46 if (!condition) { | 239 if (!condition) { |
47 this.reportError(c, reason); | 240 this.reportError(c, reason); |
48 throw new Error(reason); | 241 throw new Error(reason); |
49 } | 242 } |
50 } | 243 } |
51 | 244 |
52 getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { | 245 getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { |
53 for (let parent = n; parent; parent = parent.parent) { | 246 for (let parent = n; parent; parent = parent.parent) { |
54 if (parent.kind === kind) return parent; | 247 if (parent.kind === kind) return parent; |
55 } | 248 } |
56 return null; | 249 return null; |
57 } | 250 } |
58 | 251 |
59 hasAncestor(n: ts.Node, kind: ts.SyntaxKind): boolean { return !!this.getAnces
tor(n, kind); } | 252 hasAncestor(n: ts.Node, kind: ts.SyntaxKind): boolean { return !!getAncestor(n
, kind); } |
60 | 253 |
61 hasAnnotation(decorators: ts.NodeArray<ts.Decorator>, name: string): boolean { | 254 hasAnnotation(decorators: ts.NodeArray<ts.Decorator>, name: string): boolean { |
62 if (!decorators) return false; | 255 if (!decorators) return false; |
63 return decorators.some((d) => { | 256 return decorators.some((d) => { |
64 let decName = ident(d.expression); | 257 let decName = ident(d.expression); |
65 if (decName === name) return true; | 258 if (decName === name) return true; |
66 if (d.expression.kind !== ts.SyntaxKind.CallExpression) return false; | 259 if (d.expression.kind !== ts.SyntaxKind.CallExpression) return false; |
67 let callExpr = (<ts.CallExpression>d.expression); | 260 let callExpr = (<ts.CallExpression>d.expression); |
68 decName = ident(callExpr.expression); | 261 decName = ident(callExpr.expression); |
69 return decName === name; | 262 return decName === name; |
70 }); | 263 }); |
71 } | 264 } |
72 | 265 |
73 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean { | 266 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean { |
74 return n && (n.flags & flag) !== 0 || false; | 267 return n && (n.flags & flag) !== 0 || false; |
75 } | 268 } |
76 | 269 |
77 isConst(decl: ClassLike) { | |
78 return this.hasAnnotation(decl.decorators, 'CONST') || | |
79 (<ts.NodeArray<ts.Declaration>>decl.members).some((m) => { | |
80 if (m.kind !== ts.SyntaxKind.Constructor) return false; | |
81 return this.hasAnnotation(m.decorators, 'CONST'); | |
82 }); | |
83 } | |
84 | |
85 maybeDestructureIndexType(node: ts.TypeLiteralNode): [ts.TypeNode, ts.TypeNode
] { | |
86 let members = node.members; | |
87 if (members.length !== 1 || members[0].kind !== ts.SyntaxKind.IndexSignature
) { | |
88 return null; | |
89 } | |
90 let indexSig = <ts.IndexSignatureDeclaration>(members[0]); | |
91 if (indexSig.parameters.length > 1) { | |
92 this.reportError(indexSig, 'Expected an index signature to have a single p
arameter'); | |
93 } | |
94 return [indexSig.parameters[0].type, indexSig.type]; | |
95 } | |
96 | |
97 | |
98 getRelativeFileName(fileName: string): string { | 270 getRelativeFileName(fileName: string): string { |
99 return this.transpiler.getRelativeFileName(fileName); | 271 return this.transpiler.getRelativeFileName(fileName); |
100 } | 272 } |
101 | 273 |
102 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { | 274 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { |
103 if (n.typeArguments) { | 275 if (n.typeArguments) { |
104 // If it's a single type argument `<void>`, ignore it and emit nothing. | 276 // If it's a single type argument `<void>`, ignore it and emit nothing. |
105 // This is particularly useful for `Promise<void>`, see | 277 // This is particularly useful for `Promise<void>`, see |
106 // https://github.com/dart-lang/sdk/issues/2231#issuecomment-108313639 | 278 // https://github.com/dart-lang/sdk/issues/2231#issuecomment-108313639 |
107 if (n.typeArguments.length === 1 && n.typeArguments[0].kind === ts.SyntaxK
ind.VoidKeyword) { | 279 if (n.typeArguments.length === 1 && n.typeArguments[0].kind === ts.SyntaxK
ind.VoidKeyword) { |
108 return; | 280 return; |
109 } | 281 } |
110 this.emitNoSpace('<'); | 282 this.emitNoSpace('<'); |
111 this.visitList(n.typeArguments); | 283 this.visitList(n.typeArguments); |
112 this.emit('>'); | 284 this.emitNoSpace('>'); |
113 } | 285 } |
114 } | 286 } |
| 287 |
| 288 visitParameters(parameters: ts.ParameterDeclaration[]) { |
| 289 this.emitNoSpace('('); |
| 290 let firstInitParamIdx = 0; |
| 291 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { |
| 292 // ObjectBindingPatterns are handled within the parameter visit. |
| 293 let isOpt = parameters[firstInitParamIdx].initializer || |
| 294 parameters[firstInitParamIdx].questionToken || |
| 295 parameters[firstInitParamIdx].dotDotDotToken; |
| 296 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj
ectBindingPattern) { |
| 297 break; |
| 298 } |
| 299 } |
| 300 |
| 301 if (firstInitParamIdx !== 0) { |
| 302 let requiredParams = parameters.slice(0, firstInitParamIdx); |
| 303 this.visitList(requiredParams); |
| 304 } |
| 305 |
| 306 if (firstInitParamIdx !== parameters.length) { |
| 307 if (firstInitParamIdx !== 0) this.emitNoSpace(','); |
| 308 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le
ngth); |
| 309 this.emit('['); |
| 310 this.visitList(positionalOptional); |
| 311 this.emitNoSpace(']'); |
| 312 } |
| 313 |
| 314 this.emitNoSpace(')'); |
| 315 } |
115 } | 316 } |
OLD | NEW |