| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * This is for use in extracting messages from a Dart program | 6 * This is for use in extracting messages from a Dart program |
| 7 * using the Intl.message() mechanism and writing them to a file for | 7 * using the Intl.message() mechanism and writing them to a file for |
| 8 * translation. This provides only the stub of a mechanism, because it | 8 * translation. This provides only the stub of a mechanism, because it |
| 9 * doesn't define how the file should be written. It provides an | 9 * doesn't define how the file should be written. It provides an |
| 10 * [IntlMessage] class that holds the extracted data and [parseString] | 10 * [IntlMessage] class that holds the extracted data and [parseString] |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 * as that will be the nearest parent of the Intl.message invocation. | 105 * as that will be the nearest parent of the Intl.message invocation. |
| 106 */ | 106 */ |
| 107 FormalParameterList parameters; | 107 FormalParameterList parameters; |
| 108 String name; | 108 String name; |
| 109 | 109 |
| 110 /** Return true if [node] matches the pattern we expect for Intl.message() */ | 110 /** Return true if [node] matches the pattern we expect for Intl.message() */ |
| 111 bool looksLikeIntlMessage(MethodInvocation node) { | 111 bool looksLikeIntlMessage(MethodInvocation node) { |
| 112 const validNames = const ["message", "plural", "gender", "select"]; | 112 const validNames = const ["message", "plural", "gender", "select"]; |
| 113 if (!validNames.contains(node.methodName.name)) return false; | 113 if (!validNames.contains(node.methodName.name)) return false; |
| 114 if (!(node.target is SimpleIdentifier)) return false; | 114 if (!(node.target is SimpleIdentifier)) return false; |
| 115 SimpleIdentifier target = node.target; | 115 return node.target.token.toString() == "Intl"; |
| 116 if (target.token.toString() != "Intl") return false; | |
| 117 return true; | |
| 118 } | 116 } |
| 119 | 117 |
| 120 Message _expectedInstance(String type) { | 118 Message _expectedInstance(String type) { |
| 121 switch (type) { | 119 switch (type) { |
| 122 case 'message' : return new MainMessage(); | 120 case 'message' : return new MainMessage(); |
| 123 case 'plural' : return new Plural(); | 121 case 'plural' : return new Plural(); |
| 124 case 'gender' : return new Gender(); | 122 case 'gender' : return new Gender(); |
| 125 case 'select' : return new Select(); | 123 case 'select' : return new Select(); |
| 126 default: return null; | 124 default: return null; |
| 127 } | 125 } |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 168 } | 166 } |
| 169 | 167 |
| 170 /** | 168 /** |
| 171 * Examine method invocations to see if they look like calls to Intl.message. | 169 * Examine method invocations to see if they look like calls to Intl.message. |
| 172 * If we've found one, stop recursing. This is important because we can have | 170 * If we've found one, stop recursing. This is important because we can have |
| 173 * Intl.message(...Intl.plural...) and we don't want to treat the inner | 171 * Intl.message(...Intl.plural...) and we don't want to treat the inner |
| 174 * plural as if it was an outermost message. | 172 * plural as if it was an outermost message. |
| 175 */ | 173 */ |
| 176 void visitMethodInvocation(MethodInvocation node) { | 174 void visitMethodInvocation(MethodInvocation node) { |
| 177 if (!addIntlMessage(node)) { | 175 if (!addIntlMessage(node)) { |
| 178 return super.visitMethodInvocation(node); | 176 super.visitMethodInvocation(node); |
| 179 } | 177 } |
| 180 } | 178 } |
| 181 | 179 |
| 182 /** | 180 /** |
| 183 * Check that the node looks like an Intl.message invocation, and create | 181 * Check that the node looks like an Intl.message invocation, and create |
| 184 * the [IntlMessage] object from it and store it in [messages]. Return true | 182 * the [IntlMessage] object from it and store it in [messages]. Return true |
| 185 * if we successfully extracted a message and should stop looking. Return | 183 * if we successfully extracted a message and should stop looking. Return |
| 186 * false if we didn't, so should continue recursing. | 184 * false if we didn't, so should continue recursing. |
| 187 */ | 185 */ |
| 188 bool addIntlMessage(MethodInvocation node) { | 186 bool addIntlMessage(MethodInvocation node) { |
| 189 if (!looksLikeIntlMessage(node)) return false; | 187 if (!looksLikeIntlMessage(node)) return false; |
| 190 var reason = checkValidity(node); | 188 var reason = checkValidity(node); |
| 191 if (reason != null) { | 189 if (reason != null) { |
| 192 if (!suppressWarnings) { | 190 if (!suppressWarnings) { |
| 193 var err = new StringBuffer(); | 191 var err = new StringBuffer() |
| 194 err.write("Skipping invalid Intl.message invocation\n <$node>\n"); | 192 ..write("Skipping invalid Intl.message invocation\n <$node>\n") |
| 195 err.write(" reason: $reason\n"); | 193 ..writeAll([" reason: $reason\n", _reportErrorLocation(node)]); |
| 196 err.write(_reportErrorLocation(node)); | |
| 197 warnings.add(err.toString()); | 194 warnings.add(err.toString()); |
| 198 print(err); | 195 print(err); |
| 199 } | 196 } |
| 200 // We found one, but it's not valid. Stop recursing. | 197 // We found one, but it's not valid. Stop recursing. |
| 201 return true; | 198 return true; |
| 202 } | 199 } |
| 203 var message; | 200 var message; |
| 204 if (node.methodName.name == "message") { | 201 if (node.methodName.name == "message") { |
| 205 message = messageFromIntlMessageCall(node); | 202 message = messageFromIntlMessageCall(node); |
| 206 } else { | 203 } else { |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 245 */ | 242 */ |
| 246 MainMessage messageFromIntlMessageCall(MethodInvocation node) { | 243 MainMessage messageFromIntlMessageCall(MethodInvocation node) { |
| 247 | 244 |
| 248 void extractFromIntlCall(MainMessage message, List arguments) { | 245 void extractFromIntlCall(MainMessage message, List arguments) { |
| 249 try { | 246 try { |
| 250 var interpolation = new InterpolationVisitor(message); | 247 var interpolation = new InterpolationVisitor(message); |
| 251 arguments.first.accept(interpolation); | 248 arguments.first.accept(interpolation); |
| 252 message.messagePieces.addAll(interpolation.pieces); | 249 message.messagePieces.addAll(interpolation.pieces); |
| 253 } on IntlMessageExtractionException catch (e) { | 250 } on IntlMessageExtractionException catch (e) { |
| 254 message = null; | 251 message = null; |
| 255 var err = new StringBuffer(); | 252 var err = new StringBuffer() |
| 256 err.write("Error $e\n"); | 253 ..writeAll(["Error ", e, "\nProcessing <", node, ">\n"]) |
| 257 err.write("Processing <$node>\n"); | 254 ..write(_reportErrorLocation(node)); |
| 258 err.write(_reportErrorLocation(node)); | |
| 259 print(err); | 255 print(err); |
| 260 warnings.add(err); | 256 warnings.add(err); |
| 261 } | 257 } |
| 262 } | 258 } |
| 263 | 259 |
| 264 void setValue(MainMessage message, String fieldName, Object fieldValue) { | 260 void setValue(MainMessage message, String fieldName, Object fieldValue) { |
| 265 message[fieldName] = fieldValue; | 261 message[fieldName] = fieldValue; |
| 266 } | 262 } |
| 267 | 263 |
| 268 return _messageFromNode(node, extractFromIntlCall, setValue); | 264 return _messageFromNode(node, extractFromIntlCall, setValue); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 297 * Given an interpolation, find all of its chunks, validate that they are only | 293 * Given an interpolation, find all of its chunks, validate that they are only |
| 298 * simple variable substitutions or else Intl.plural/gender calls, | 294 * simple variable substitutions or else Intl.plural/gender calls, |
| 299 * and keep track of the pieces of text so that other parts | 295 * and keep track of the pieces of text so that other parts |
| 300 * of the program can deal with the simple string sections and the generated | 296 * of the program can deal with the simple string sections and the generated |
| 301 * parts separately. Note that this is a SimpleASTVisitor, so it only | 297 * parts separately. Note that this is a SimpleASTVisitor, so it only |
| 302 * traverses one level of children rather than automatically recursing. If we | 298 * traverses one level of children rather than automatically recursing. If we |
| 303 * find a plural or gender, which requires recursion, we do it with a separate | 299 * find a plural or gender, which requires recursion, we do it with a separate |
| 304 * special-purpose visitor. | 300 * special-purpose visitor. |
| 305 */ | 301 */ |
| 306 class InterpolationVisitor extends SimpleASTVisitor { | 302 class InterpolationVisitor extends SimpleASTVisitor { |
| 307 Message message; | 303 final Message message; |
| 308 | 304 |
| 309 InterpolationVisitor(this.message); | 305 InterpolationVisitor(this.message); |
| 310 | 306 |
| 311 List pieces = []; | 307 List pieces = []; |
| 312 String get extractedMessage => pieces.join(); | 308 String get extractedMessage => pieces.join(); |
| 313 | 309 |
| 314 void visitAdjacentStrings(AdjacentStrings node) { | 310 void visitAdjacentStrings(AdjacentStrings node) { |
| 315 node.visitChildren(this); | 311 node.visitChildren(this); |
| 316 super.visitAdjacentStrings(node); | 312 super.visitAdjacentStrings(node); |
| 317 } | 313 } |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 366 /** | 362 /** |
| 367 * A visitor to extract information from Intl.plural/gender sends. Note that | 363 * A visitor to extract information from Intl.plural/gender sends. Note that |
| 368 * this is a SimpleASTVisitor, so it doesn't automatically recurse. So this | 364 * this is a SimpleASTVisitor, so it doesn't automatically recurse. So this |
| 369 * needs to be called where we expect a plural or gender immediately below. | 365 * needs to be called where we expect a plural or gender immediately below. |
| 370 */ | 366 */ |
| 371 class PluralAndGenderVisitor extends SimpleASTVisitor { | 367 class PluralAndGenderVisitor extends SimpleASTVisitor { |
| 372 /** | 368 /** |
| 373 * A plural or gender always exists in the context of a parent message, | 369 * A plural or gender always exists in the context of a parent message, |
| 374 * which could in turn also be a plural or gender. | 370 * which could in turn also be a plural or gender. |
| 375 */ | 371 */ |
| 376 ComplexMessage parent; | 372 final ComplexMessage parent; |
| 377 | 373 |
| 378 /** | 374 /** |
| 379 * The pieces of the message. We are given an initial version of this | 375 * The pieces of the message. We are given an initial version of this |
| 380 * from our parent and we add to it as we find additional information. | 376 * from our parent and we add to it as we find additional information. |
| 381 */ | 377 */ |
| 382 List pieces; | 378 List pieces; |
| 383 | 379 |
| 384 PluralAndGenderVisitor(this.pieces, this.parent) : super() {} | |
| 385 | |
| 386 /** This will be set to true if we find a plural or gender. */ | 380 /** This will be set to true if we find a plural or gender. */ |
| 387 bool foundPluralOrGender = false; | 381 bool foundPluralOrGender = false; |
| 388 | 382 |
| 383 PluralAndGenderVisitor(this.pieces, this.parent) : super(); |
| 384 |
| 389 visitInterpolationExpression(InterpolationExpression node) { | 385 visitInterpolationExpression(InterpolationExpression node) { |
| 390 // TODO(alanknight): Provide better errors for malformed expressions. | 386 // TODO(alanknight): Provide better errors for malformed expressions. |
| 391 if (!looksLikePluralOrGender(node.expression)) return; | 387 if (!looksLikePluralOrGender(node.expression)) return; |
| 392 var reason = checkValidity(node.expression); | 388 var reason = checkValidity(node.expression); |
| 393 if (reason != null) throw reason; | 389 if (reason != null) throw reason; |
| 394 var message = messageFromMethodInvocation(node.expression); | 390 var message = messageFromMethodInvocation(node.expression); |
| 395 foundPluralOrGender = true; | 391 foundPluralOrGender = true; |
| 396 pieces.add(message); | 392 pieces.add(message); |
| 397 super.visitInterpolationExpression(node); | 393 super.visitInterpolationExpression(node); |
| 398 } | 394 } |
| 399 | 395 |
| 400 visitMethodInvocation(MethodInvocation node) { | 396 visitMethodInvocation(MethodInvocation node) { |
| 401 pieces.add(messageFromMethodInvocation(node)); | 397 pieces.add(messageFromMethodInvocation(node)); |
| 402 super.visitMethodInvocation(node); | 398 super.visitMethodInvocation(node); |
| 403 } | 399 } |
| 404 | 400 |
| 405 /** Return true if [node] matches the pattern for plural or gender message.*/ | 401 /** Return true if [node] matches the pattern for plural or gender message.*/ |
| 406 bool looksLikePluralOrGender(MethodInvocation node) { | 402 bool looksLikePluralOrGender(MethodInvocation node) { |
| 407 if (!["plural", "gender", "select"].contains(node.methodName.name)) { | 403 if (!["plural", "gender", "select"].contains(node.methodName.name)) { |
| 408 return false; | 404 return false; |
| 409 } | 405 } |
| 410 if (!(node.target is SimpleIdentifier)) return false; | 406 if (!(node.target is SimpleIdentifier)) return false; |
| 411 SimpleIdentifier target = node.target; | 407 return node.target.token.toString() == "Intl"; |
| 412 if (target.token.toString() != "Intl") return false; | |
| 413 return true; | |
| 414 } | 408 } |
| 415 | 409 |
| 416 /** | 410 /** |
| 417 * Returns a String describing why the node is invalid, or null if no | 411 * Returns a String describing why the node is invalid, or null if no |
| 418 * reason is found, so it's presumed valid. | 412 * reason is found, so it's presumed valid. |
| 419 */ | 413 */ |
| 420 String checkValidity(MethodInvocation node) { | 414 String checkValidity(MethodInvocation node) { |
| 421 // TODO(alanknight): Add reasonable validity checks. | 415 // TODO(alanknight): Add reasonable validity checks. |
| 422 } | 416 } |
| 423 | 417 |
| 424 /** | 418 /** |
| 425 * Create a MainMessage from [node] using the name and | 419 * Create a MainMessage from [node] using the name and |
| 426 * parameters of the last function/method declaration we encountered | 420 * parameters of the last function/method declaration we encountered
e |
| 427 * and the parameters to the Intl.message call. | 421 * and the parameters to the Intl.message call. |
| 428 */ | 422 */ |
| 429 Message messageFromMethodInvocation(MethodInvocation node) { | 423 Message messageFromMethodInvocation(MethodInvocation node) { |
| 430 var message; | 424 var message; |
| 431 switch(node.methodName.name) { | 425 switch(node.methodName.name) { |
| 432 case "gender" : message = new Gender(); break; | 426 case "gender" : message = new Gender(); break; |
| 433 case "plural" : message = new Plural(); break; | 427 case "plural" : message = new Plural(); break; |
| 434 case "select" : message = new Select(); break; | 428 case "select" : message = new Select(); break; |
| 435 default: throw new IntlMessageExtractionException( | 429 default: throw new IntlMessageExtractionException( |
| 436 "Invalid plural/gender/select message"); | 430 "Invalid plural/gender/select message"); |
| 437 } | 431 } |
| 438 message.parent = parent; | 432 message.parent = parent; |
| 439 | 433 |
| 440 var arguments = message.argumentsOfInterestFor(node); | 434 var arguments = message.argumentsOfInterestFor(node); |
| 441 arguments.forEach((key, value) { | 435 arguments.forEach((key, value) { |
| 442 try { | 436 try { |
| 443 var interpolation = new InterpolationVisitor(message); | 437 var interpolation = new InterpolationVisitor(message); |
| 444 value.accept(interpolation); | 438 value.accept(interpolation); |
| 445 message[key] = interpolation.pieces; | 439 message[key] = interpolation.pieces; |
| 446 } on IntlMessageExtractionException catch (e) { | 440 } on IntlMessageExtractionException catch (e) { |
| 447 message = null; | 441 message = null; |
| 448 var err = new StringBuffer(); | 442 var err = new StringBuffer() |
| 449 err.write("Error $e"); | 443 ..writeAll(["Error ", $e, "\nProcessing <", node, ">"]) |
| 450 err.write("Processing <$node>"); | 444 ..write(_reportErrorLocation(node)); |
| 451 err.write(_reportErrorLocation(node)); | |
| 452 print(err); | 445 print(err); |
| 453 warnings.add(err); | 446 warnings.add(err); |
| 454 } | 447 } |
| 455 }); | 448 }); |
| 456 var mainArg = node.argumentList.arguments.firstWhere( | 449 var mainArg = node.argumentList.arguments.firstWhere( |
| 457 (each) => each is! NamedExpression); | 450 (each) => each is! NamedExpression); |
| 458 if (mainArg is SimpleStringLiteral) { | 451 if (mainArg is SimpleStringLiteral) { |
| 459 message.mainArgument = mainArg.toString(); | 452 message.mainArgument = mainArg.toString(); |
| 460 } else { | 453 } else { |
| 461 message.mainArgument = mainArg.name; | 454 message.mainArgument = mainArg.name; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 472 * A message describing the error. | 465 * A message describing the error. |
| 473 */ | 466 */ |
| 474 final String message; | 467 final String message; |
| 475 | 468 |
| 476 /** | 469 /** |
| 477 * Creates a new exception with an optional error [message]. | 470 * Creates a new exception with an optional error [message]. |
| 478 */ | 471 */ |
| 479 const IntlMessageExtractionException([this.message = ""]); | 472 const IntlMessageExtractionException([this.message = ""]); |
| 480 | 473 |
| 481 String toString() => "IntlMessageExtractionException: $message"; | 474 String toString() => "IntlMessageExtractionException: $message"; |
| 482 } | 475 } |
| OLD | NEW |