| OLD | NEW |
| 1 import * as dartStyle from 'dart-style'; | 1 import * as dartStyle from 'dart-style'; |
| 2 import * as path from 'path'; |
| 2 import * as ts from 'typescript'; | 3 import * as ts from 'typescript'; |
| 3 | 4 |
| 4 import {OutputContext, Transpiler} from './main'; | 5 import {OutputContext, Transpiler} from './main'; |
| 5 | 6 |
| 7 export type ResolvedTypeMap = { |
| 8 [name: string]: ts.TypeNode |
| 9 }; |
| 10 |
| 11 /*** |
| 12 * Options for how we display a TypeScript type as a Dart type. |
| 13 */ |
| 14 export interface TypeDisplayOptions { |
| 15 /** |
| 16 * We are displaying the type inside a comment so we don't have to restrict to
valid Dart syntax. |
| 17 * For example, we can display string literal type using the regular TypeScrip
t syntax. |
| 18 */ |
| 19 insideComment?: boolean; |
| 20 /** |
| 21 * Dart has additional restrictions for what types are valid to emit inside a
type argument. For |
| 22 * example, "void" is not valid inside a type argument so Null has to be used
instead. |
| 23 */ |
| 24 insideTypeArgument?: boolean; |
| 25 /** |
| 26 * Indicates that we should not append an additional comment indicating what t
he true TypeScript |
| 27 * type was for cases where Dart cannot express the type precisely. |
| 28 */ |
| 29 hideComment?: boolean; |
| 30 |
| 31 /** |
| 32 * Parameter declarations to substitute |
| 33 */ |
| 34 typeArguments?: ts.TypeNode[]; |
| 35 |
| 36 resolvedTypeArguments?: ResolvedTypeMap; |
| 37 } |
| 38 |
| 6 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; | 39 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; |
| 7 export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableD
eclaration | | 40 export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableD
eclaration | |
| 8 ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration; | 41 ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration; |
| 9 | 42 |
| 43 export interface ExtendedInterfaceDeclaration extends ts.InterfaceDeclaration { |
| 44 /** |
| 45 * VariableDeclaration associated with this interface that we want to treat as
the concrete |
| 46 * location of this interface to enable interfaces that act like constructors. |
| 47 * Because Dart does not permit calling objects like constructors we have to a
dd this workaround. |
| 48 */ |
| 49 classLikeVariableDeclaration?: ts.VariableDeclaration; |
| 50 } |
| 51 |
| 10 export type Set = { | 52 export type Set = { |
| 11 [s: string]: boolean | 53 [s: string]: boolean |
| 12 }; | 54 }; |
| 13 | 55 |
| 14 export function ident(n: ts.Node): string { | 56 export function ident(n: ts.Node): string { |
| 15 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; | 57 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; |
| 58 if (n.kind === ts.SyntaxKind.FirstLiteralToken) { |
| 59 return (n as ts.LiteralLikeNode).text; |
| 60 } |
| 16 if (n.kind === ts.SyntaxKind.QualifiedName) { | 61 if (n.kind === ts.SyntaxKind.QualifiedName) { |
| 17 let qname = (<ts.QualifiedName>n); | 62 let qname = (<ts.QualifiedName>n); |
| 18 let leftName = ident(qname.left); | 63 let leftName = ident(qname.left); |
| 19 if (leftName) return leftName + '.' + ident(qname.right); | 64 if (leftName) return leftName + '.' + ident(qname.right); |
| 20 } | 65 } |
| 21 return null; | 66 return null; |
| 22 } | 67 } |
| 23 | 68 |
| 24 export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration):
boolean { | 69 export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration):
boolean { |
| 25 return ifDecl.members && ifDecl.members.length === 1 && | 70 return ifDecl.members && ifDecl.members.length === 1 && |
| (...skipping 27 matching lines...) Expand all Loading... |
| 53 | 98 |
| 54 export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 99 export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { |
| 55 if (isFunctionType(type, tc)) return true; | 100 if (isFunctionType(type, tc)) return true; |
| 56 if (type.kind === ts.SyntaxKind.TypeReference) { | 101 if (type.kind === ts.SyntaxKind.TypeReference) { |
| 57 if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call
).length > 0) | 102 if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call
).length > 0) |
| 58 return true; | 103 return true; |
| 59 } | 104 } |
| 60 return false; | 105 return false; |
| 61 } | 106 } |
| 62 | 107 |
| 108 export function supportedAliasType(alias: ts.TypeAliasDeclaration): boolean { |
| 109 let kind = alias.type.kind; |
| 110 return kind === ts.SyntaxKind.TypeLiteral || kind === ts.SyntaxKind.FunctionTy
pe; |
| 111 } |
| 112 |
| 63 export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 113 export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { |
| 64 let kind = type.kind; | 114 let kind = type.kind; |
| 65 if (kind === ts.SyntaxKind.FunctionType) return true; | 115 if (kind === ts.SyntaxKind.FunctionType) return true; |
| 66 if (kind === ts.SyntaxKind.TypeReference) { | 116 if (kind === ts.SyntaxKind.TypeReference) { |
| 67 let t = tc.getTypeAtLocation(type); | 117 let t = tc.getTypeAtLocation(type); |
| 68 if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; | 118 if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; |
| 69 } | 119 } |
| 70 | 120 |
| 121 if (kind === ts.SyntaxKind.IntersectionType) { |
| 122 let types = (<ts.IntersectionTypeNode>type).types; |
| 123 for (let i = 0; i < types.length; ++i) { |
| 124 if (isFunctionType(types[i], tc)) { |
| 125 return true; |
| 126 } |
| 127 } |
| 128 return false; |
| 129 } |
| 130 |
| 71 if (kind === ts.SyntaxKind.UnionType) { | 131 if (kind === ts.SyntaxKind.UnionType) { |
| 72 let types = (<ts.UnionTypeNode>type).types; | 132 let types = (<ts.UnionTypeNode>type).types; |
| 73 for (let i = 0; i < types.length; ++i) { | 133 for (let i = 0; i < types.length; ++i) { |
| 74 if (!isFunctionType(types[i], tc)) { | 134 if (!isFunctionType(types[i], tc)) { |
| 75 return false; | 135 return false; |
| 76 } | 136 } |
| 77 } | 137 } |
| 78 return true; | 138 return true; |
| 79 } | 139 } |
| 80 // Warning: if the kind is a reference type and the reference is to an | 140 // 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 | 141 // interface that only has a call member we will not return that it is a |
| 82 // function type. | 142 // function type. |
| 83 if (kind === ts.SyntaxKind.TypeLiteral) { | 143 if (kind === ts.SyntaxKind.TypeLiteral) { |
| 84 let members = (<ts.TypeLiteralNode>type).members; | 144 let members = (<ts.TypeLiteralNode>type).members; |
| 85 for (let i = 0; i < members.length; ++i) { | 145 for (let i = 0; i < members.length; ++i) { |
| 86 if (members[i].kind !== ts.SyntaxKind.CallSignature) { | 146 if (members[i].kind !== ts.SyntaxKind.CallSignature) { |
| 87 return false; | 147 return false; |
| 88 } | 148 } |
| 89 } | 149 } |
| 90 return true; | 150 return true; |
| 91 } | 151 } |
| 92 return false; | 152 return false; |
| 93 } | 153 } |
| 94 | 154 |
| 155 export function isThisParameter(param: ts.ParameterDeclaration): boolean { |
| 156 return param.name && param.name.kind === ts.SyntaxKind.Identifier && |
| 157 (param.name as ts.Identifier).text === 'this'; |
| 158 } |
| 159 |
| 160 /** |
| 161 * Dart does not have a concept of binding the type of the "this" parameter to a
method. |
| 162 */ |
| 163 export function filterThisParameter(params: ts.ParameterDeclaration[]): ts.Param
eterDeclaration[] { |
| 164 let ret: ts.ParameterDeclaration[] = []; |
| 165 for (let i = 0; i < params.length; i++) { |
| 166 let param = params[i]; |
| 167 if (!isThisParameter(param)) { |
| 168 ret.push(param); |
| 169 } |
| 170 } |
| 171 return ret; |
| 172 } |
| 173 |
| 95 export function isTypeNode(node: ts.Node): boolean { | 174 export function isTypeNode(node: ts.Node): boolean { |
| 96 switch (node.kind) { | 175 switch (node.kind) { |
| 176 case ts.SyntaxKind.IntersectionType: |
| 97 case ts.SyntaxKind.UnionType: | 177 case ts.SyntaxKind.UnionType: |
| 178 case ts.SyntaxKind.ParenthesizedType: |
| 98 case ts.SyntaxKind.TypeReference: | 179 case ts.SyntaxKind.TypeReference: |
| 99 case ts.SyntaxKind.TypeLiteral: | 180 case ts.SyntaxKind.TypeLiteral: |
| 100 case ts.SyntaxKind.LastTypeNode: | 181 case ts.SyntaxKind.LastTypeNode: |
| 101 case ts.SyntaxKind.ArrayType: | 182 case ts.SyntaxKind.ArrayType: |
| 102 case ts.SyntaxKind.TypePredicate: | 183 case ts.SyntaxKind.TypePredicate: |
| 103 case ts.SyntaxKind.TypeQuery: | 184 case ts.SyntaxKind.TypeQuery: |
| 104 case ts.SyntaxKind.TupleType: | 185 case ts.SyntaxKind.TupleType: |
| 105 case ts.SyntaxKind.NumberKeyword: | 186 case ts.SyntaxKind.NumberKeyword: |
| 106 case ts.SyntaxKind.StringKeyword: | 187 case ts.SyntaxKind.StringKeyword: |
| 107 case ts.SyntaxKind.VoidKeyword: | 188 case ts.SyntaxKind.VoidKeyword: |
| 189 case ts.SyntaxKind.NullKeyword: |
| 190 case ts.SyntaxKind.UndefinedKeyword: |
| 108 case ts.SyntaxKind.BooleanKeyword: | 191 case ts.SyntaxKind.BooleanKeyword: |
| 109 case ts.SyntaxKind.AnyKeyword: | 192 case ts.SyntaxKind.AnyKeyword: |
| 110 case ts.SyntaxKind.FunctionType: | 193 case ts.SyntaxKind.FunctionType: |
| 194 case ts.SyntaxKind.ThisType: |
| 111 return true; | 195 return true; |
| 112 default: | 196 default: |
| 113 return false; | 197 return false; |
| 114 } | 198 } |
| 115 } | 199 } |
| 116 | 200 |
| 117 export function isCallable(decl: ClassLike): boolean { | 201 export function isCallable(decl: ClassLike): boolean { |
| 118 let members = decl.members as Array<ts.ClassElement>; | 202 let members = decl.members as Array<ts.ClassElement>; |
| 119 return members.some((member) => { return member.kind === ts.SyntaxKind.CallSig
nature; }); | 203 return members.some((member) => { return member.kind === ts.SyntaxKind.CallSig
nature; }); |
| 120 } | 204 } |
| (...skipping 13 matching lines...) Expand all Loading... |
| 134 } | 218 } |
| 135 | 219 |
| 136 export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { | 220 export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { |
| 137 for (let parent = n; parent; parent = parent.parent) { | 221 for (let parent = n; parent; parent = parent.parent) { |
| 138 if (parent.kind === kind) return parent; | 222 if (parent.kind === kind) return parent; |
| 139 } | 223 } |
| 140 return null; | 224 return null; |
| 141 } | 225 } |
| 142 | 226 |
| 143 export function getEnclosingClass(n: ts.Node): ClassLike { | 227 export function getEnclosingClass(n: ts.Node): ClassLike { |
| 144 for (let parent = n.parent; parent; parent = parent.parent) { | 228 while (n) { |
| 145 if (parent.kind === ts.SyntaxKind.ClassDeclaration || | 229 if (n.kind === ts.SyntaxKind.ClassDeclaration || |
| 146 parent.kind === ts.SyntaxKind.InterfaceDeclaration) { | 230 n.kind === ts.SyntaxKind.InterfaceDeclaration) { |
| 147 return <ClassLike>parent; | 231 return <ClassLike>n; |
| 148 } | 232 } |
| 233 n = n.parent; |
| 149 } | 234 } |
| 150 return null; | 235 return null; |
| 151 } | 236 } |
| 152 | 237 |
| 153 export function isConstCall(node: ts.CallExpression): boolean { | 238 export function isConstCall(node: ts.CallExpression): boolean { |
| 154 return node && ident(node.expression) === 'CONST_EXPR'; | 239 return node && ident(node.expression) === 'CONST_EXPR'; |
| 155 } | 240 } |
| 156 | 241 |
| 157 export function isInsideConstExpr(node: ts.Node): boolean { | 242 export function isInsideConstExpr(node: ts.Node): boolean { |
| 158 return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpr
ession)); | 243 return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpr
ession)); |
| 159 } | 244 } |
| 160 | 245 |
| 161 export function formatType(s: string, comment: string, insideCodeComment: boolea
n): string { | 246 export function formatType(s: string, comment: string, options: TypeDisplayOptio
ns): string { |
| 162 if (!comment) { | 247 if (!comment || options.hideComment) { |
| 163 return s; | 248 return s; |
| 164 } else if (insideCodeComment) { | 249 } else if (options.insideComment) { |
| 165 // When inside a comment we only need to emit the comment version which | 250 // 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 | 251 // is the syntax we would like to use if Dart supported all language |
| 167 // features we would like to use for interop. | 252 // features we would like to use for interop. |
| 168 return comment; | 253 return comment; |
| 169 } else { | 254 } else { |
| 170 let sb = s + '/*'; | 255 let sb = s + '/*'; |
| 171 // Check if the comment is a valid type name in which case it is safe to use
the Dart code | 256 // 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. | 257 // written in comments syntax. |
| 173 const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;'; | 258 const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;'; |
| 174 comment = comment.trim(); | 259 comment = comment.trim(); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 195 | 280 |
| 196 visit(n: ts.Node) { this.transpiler.visit(n); } | 281 visit(n: ts.Node) { this.transpiler.visit(n); } |
| 197 pushContext(context: OutputContext) { this.transpiler.pushContext(context); } | 282 pushContext(context: OutputContext) { this.transpiler.pushContext(context); } |
| 198 popContext() { this.transpiler.popContext(); } | 283 popContext() { this.transpiler.popContext(); } |
| 199 emit(s: string) { this.transpiler.emit(s); } | 284 emit(s: string) { this.transpiler.emit(s); } |
| 200 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); } | 285 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); } |
| 201 emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); } | 286 emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); } |
| 202 maybeLineBreak() { return this.transpiler.maybeLineBreak(); } | 287 maybeLineBreak() { return this.transpiler.maybeLineBreak(); } |
| 203 enterCodeComment() { return this.transpiler.enterCodeComment(); } | 288 enterCodeComment() { return this.transpiler.enterCodeComment(); } |
| 204 exitCodeComment() { return this.transpiler.exitCodeComment(); } | 289 exitCodeComment() { return this.transpiler.exitCodeComment(); } |
| 290 |
| 291 enterTypeArguments() { this.transpiler.enterTypeArgument(); } |
| 292 exitTypeArguments() { this.transpiler.exitTypeArgument(); } |
| 293 get insideTypeArgument() { return this.transpiler.insideTypeArgument; } |
| 294 |
| 205 get insideCodeComment() { return this.transpiler.insideCodeComment; } | 295 get insideCodeComment() { return this.transpiler.insideCodeComment; } |
| 206 | 296 |
| 207 emitImport(toEmit: string) { | 297 emitImport(toEmit: string) { |
| 208 if (!this.transpiler.importsEmitted[toEmit]) { | 298 if (!this.transpiler.importsEmitted[toEmit]) { |
| 209 this.pushContext(OutputContext.Import); | 299 this.pushContext(OutputContext.Import); |
| 210 this.emit(`import "${toEmit}";`); | 300 this.emit(`import "${toEmit}";\n`); |
| 211 this.transpiler.importsEmitted[toEmit] = true; | 301 this.transpiler.importsEmitted[toEmit] = true; |
| 212 this.popContext(); | 302 this.popContext(); |
| 213 } | 303 } |
| 214 } | 304 } |
| 215 | 305 |
| 306 emitImportForSourceFile(sourceFile: ts.SourceFile, context: ts.SourceFile) { |
| 307 if (sourceFile === context) return; |
| 308 if (sourceFile.hasNoDefaultLib) { |
| 309 // We don't want to emit imports to default lib libraries as we replace wi
th Dart equivalents |
| 310 // such as dart:html, etc. |
| 311 return; |
| 312 } |
| 313 let relativePath = path.relative(path.dirname(context.fileName), sourceFile.
fileName); |
| 314 this.emitImport(this.transpiler.getDartFileName(relativePath)); |
| 315 } |
| 316 |
| 317 |
| 216 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess
age); } | 318 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess
age); } |
| 217 | 319 |
| 218 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } | 320 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } |
| 219 | 321 |
| 220 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); } | 322 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); } |
| 221 | 323 |
| 222 visitEachIfPresent(nodes?: ts.Node[]) { | 324 visitEachIfPresent(nodes?: ts.Node[]) { |
| 223 if (nodes) this.visitEach(nodes); | 325 if (nodes) this.visitEach(nodes); |
| 224 } | 326 } |
| 225 | 327 |
| 226 visitList(nodes: ts.Node[], separator = ',') { | 328 visitList(nodes: ts.Node[], separator = ',') { |
| 227 for (let i = 0; i < nodes.length; i++) { | 329 for (let i = 0; i < nodes.length; i++) { |
| 228 this.visit(nodes[i]); | 330 this.visit(nodes[i]); |
| 229 if (i < nodes.length - 1) this.emitNoSpace(separator); | 331 if (i < nodes.length - 1) this.emitNoSpace(separator); |
| 230 } | 332 } |
| 231 } | 333 } |
| 232 | 334 |
| 335 /** |
| 336 * Returns whether any parameters were actually emitted. |
| 337 * @param nodes |
| 338 * @param separator |
| 339 */ |
| 340 visitParameterList(nodes: ts.ParameterDeclaration[]): boolean { |
| 341 let emittedParameters = false; |
| 342 for (let i = 0; i < nodes.length; ++i) { |
| 343 let param = nodes[i]; |
| 344 if (!this.insideCodeComment && isThisParameter(param)) { |
| 345 // Emit the this type in a comment as it could be of interest to Dart us
ers who are |
| 346 // calling allowInteropCaptureThis to bind a Dart method. |
| 347 this.enterCodeComment(); |
| 348 this.visit(param.type); |
| 349 this.emit('this'); |
| 350 this.exitCodeComment(); |
| 351 continue; |
| 352 } |
| 353 if (emittedParameters) { |
| 354 this.emitNoSpace(','); |
| 355 } |
| 356 this.visit(param); |
| 357 emittedParameters = true; |
| 358 } |
| 359 return emittedParameters; |
| 360 } |
| 361 |
| 233 uniqueId(name: string): string { | 362 uniqueId(name: string): string { |
| 234 const id = this.idCounter++; | 363 const id = this.idCounter++; |
| 235 return `_${name}\$\$js_facade_gen\$${id}`; | 364 return `_${name}\$\$js_facade_gen\$${id}`; |
| 236 } | 365 } |
| 237 | 366 |
| 238 assert(c: ts.Node, condition: boolean, reason: string): void { | 367 assert(c: ts.Node, condition: boolean, reason: string): void { |
| 239 if (!condition) { | 368 if (!condition) { |
| 240 this.reportError(c, reason); | 369 this.reportError(c, reason); |
| 241 throw new Error(reason); | 370 throw new Error(reason); |
| 242 } | 371 } |
| (...skipping 21 matching lines...) Expand all Loading... |
| 264 } | 393 } |
| 265 | 394 |
| 266 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean { | 395 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean { |
| 267 return n && (n.flags & flag) !== 0 || false; | 396 return n && (n.flags & flag) !== 0 || false; |
| 268 } | 397 } |
| 269 | 398 |
| 270 getRelativeFileName(fileName: string): string { | 399 getRelativeFileName(fileName: string): string { |
| 271 return this.transpiler.getRelativeFileName(fileName); | 400 return this.transpiler.getRelativeFileName(fileName); |
| 272 } | 401 } |
| 273 | 402 |
| 403 getDartFileName(fileName?: string): string { return this.transpiler.getDartFil
eName(fileName); } |
| 404 |
| 274 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { | 405 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { |
| 275 if (n.typeArguments) { | 406 if (n.typeArguments) { |
| 276 // If it's a single type argument `<void>`, ignore it and emit nothing. | |
| 277 // This is particularly useful for `Promise<void>`, see | |
| 278 // https://github.com/dart-lang/sdk/issues/2231#issuecomment-108313639 | |
| 279 if (n.typeArguments.length === 1 && n.typeArguments[0].kind === ts.SyntaxK
ind.VoidKeyword) { | |
| 280 return; | |
| 281 } | |
| 282 this.emitNoSpace('<'); | 407 this.emitNoSpace('<'); |
| 408 this.enterTypeArguments(); |
| 283 this.visitList(n.typeArguments); | 409 this.visitList(n.typeArguments); |
| 410 this.exitTypeArguments(); |
| 284 this.emitNoSpace('>'); | 411 this.emitNoSpace('>'); |
| 285 } | 412 } |
| 286 } | 413 } |
| 287 | 414 |
| 288 visitParameters(parameters: ts.ParameterDeclaration[]) { | 415 visitParameters(parameters: ts.ParameterDeclaration[]) { |
| 289 this.emitNoSpace('('); | 416 this.emitNoSpace('('); |
| 290 let firstInitParamIdx = 0; | 417 let firstInitParamIdx = 0; |
| 291 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { | 418 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { |
| 292 // ObjectBindingPatterns are handled within the parameter visit. | 419 // ObjectBindingPatterns are handled within the parameter visit. |
| 293 let isOpt = parameters[firstInitParamIdx].initializer || | 420 let isOpt = parameters[firstInitParamIdx].initializer || |
| 294 parameters[firstInitParamIdx].questionToken || | 421 parameters[firstInitParamIdx].questionToken || |
| 295 parameters[firstInitParamIdx].dotDotDotToken; | 422 parameters[firstInitParamIdx].dotDotDotToken; |
| 296 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj
ectBindingPattern) { | 423 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj
ectBindingPattern) { |
| 297 break; | 424 break; |
| 298 } | 425 } |
| 299 } | 426 } |
| 300 | 427 |
| 428 let hasValidParameters = false; |
| 301 if (firstInitParamIdx !== 0) { | 429 if (firstInitParamIdx !== 0) { |
| 302 let requiredParams = parameters.slice(0, firstInitParamIdx); | 430 let requiredParams = parameters.slice(0, firstInitParamIdx); |
| 303 this.visitList(requiredParams); | 431 hasValidParameters = this.visitParameterList(requiredParams); |
| 304 } | 432 } |
| 305 | 433 |
| 306 if (firstInitParamIdx !== parameters.length) { | 434 if (firstInitParamIdx !== parameters.length) { |
| 307 if (firstInitParamIdx !== 0) this.emitNoSpace(','); | 435 if (hasValidParameters) this.emitNoSpace(','); |
| 308 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le
ngth); | 436 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le
ngth); |
| 309 this.emit('['); | 437 this.emit('['); |
| 310 this.visitList(positionalOptional); | 438 this.visitParameterList(positionalOptional); |
| 311 this.emitNoSpace(']'); | 439 this.emitNoSpace(']'); |
| 312 } | 440 } |
| 313 | 441 |
| 314 this.emitNoSpace(')'); | 442 this.emitNoSpace(')'); |
| 315 } | 443 } |
| 316 } | 444 } |
| OLD | NEW |