| 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 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 102 /** | 102 /** |
| 103 * We keep track of the data from the last MethodDeclaration, | 103 * We keep track of the data from the last MethodDeclaration, |
| 104 * FunctionDeclaration or FunctionExpression that we saw on the way down, | 104 * FunctionDeclaration or FunctionExpression that we saw on the way down, |
| 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"]; | 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 SimpleIdentifier target = node.target; |
| 116 if (target.token.toString() != "Intl") return false; | 116 if (target.token.toString() != "Intl") return false; |
| 117 return true; | 117 return true; |
| 118 } | 118 } |
| 119 | 119 |
| 120 Message _expectedInstance(String type) { |
| 121 switch (type) { |
| 122 case 'message' : return new MainMessage(); |
| 123 case 'plural' : return new Plural(); |
| 124 case 'gender' : return new Gender(); |
| 125 case 'select' : return new Select(); |
| 126 default: return null; |
| 127 } |
| 128 } |
| 129 |
| 120 /** | 130 /** |
| 121 * Returns a String describing why the node is invalid, or null if no | 131 * Returns a String describing why the node is invalid, or null if no |
| 122 * reason is found, so it's presumed valid. | 132 * reason is found, so it's presumed valid. |
| 123 */ | 133 */ |
| 124 String checkValidity(MethodInvocation node) { | 134 String checkValidity(MethodInvocation node) { |
| 125 // The containing function cannot have named parameters. | 135 // The containing function cannot have named parameters. |
| 126 if (parameters.parameters.any((each) => each.kind == ParameterKind.NAMED)) { | 136 if (parameters.parameters.any((each) => each.kind == ParameterKind.NAMED)) { |
| 127 return "Named parameters on message functions are not supported."; | 137 return "Named parameters on message functions are not supported."; |
| 128 } | 138 } |
| 129 var arguments = node.argumentList.arguments; | 139 var arguments = node.argumentList.arguments; |
| 130 | 140 var instance = _expectedInstance(node.methodName.name); |
| 131 if (node.methodName.name == 'message') { | 141 return instance.checkValidity(node, arguments, name, parameters); |
| 132 if (!(arguments.first is StringLiteral)) { | |
| 133 return "Intl.message messages must be string literals"; | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 var namedArguments = arguments.skip(1); | |
| 138 // This seems unlikely to happen, but make sure all are NamedExpression | |
| 139 // before doing the tests below. | |
| 140 if (!namedArguments.every((each) => each is NamedExpression)) { | |
| 141 return "Message arguments except the message must be named"; | |
| 142 } | |
| 143 var notArgs = namedArguments.where( | |
| 144 (each) => each.name.label.name != 'args'); | |
| 145 var values = notArgs.map((each) => each.expression).toList(); | |
| 146 if (!values.every((each) => each is SimpleStringLiteral)) { | |
| 147 "Intl.message arguments must be simple string literals"; | |
| 148 } | |
| 149 var messageName = notArgs.firstWhere( | |
| 150 (eachArg) => eachArg.name.label.name == 'name', | |
| 151 orElse: () => null); | |
| 152 if (messageName == null) { | |
| 153 return "The 'name' argument for Intl.message must be specified"; | |
| 154 } | |
| 155 if ((messageName.expression is! SimpleStringLiteral) | |
| 156 || messageName.expression.value != name) { | |
| 157 return "The 'name' argument for Intl.message must be a simple string " | |
| 158 "literal and match the containing function name."; | |
| 159 } | |
| 160 var hasArgs = namedArguments.any((each) => each.name.label.name == 'args'); | |
| 161 var hasParameters = !parameters.parameters.isEmpty; | |
| 162 if (!hasArgs && hasParameters) { | |
| 163 return "The 'args' argument for Intl.message must be specified"; | |
| 164 } | |
| 165 return null; | |
| 166 } | 142 } |
| 167 | 143 |
| 168 /** | 144 /** |
| 169 * Record the parameters of the function or method declaration we last | 145 * Record the parameters of the function or method declaration we last |
| 170 * encountered before seeing the Intl.message call. | 146 * encountered before seeing the Intl.message call. |
| 171 */ | 147 */ |
| 172 void visitMethodDeclaration(MethodDeclaration node) { | 148 void visitMethodDeclaration(MethodDeclaration node) { |
| 173 parameters = node.parameters; | 149 parameters = node.parameters; |
| 174 name = node.name.name; | 150 name = node.name.name; |
| 175 super.visitMethodDeclaration(node); | 151 super.visitMethodDeclaration(node); |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 237 */ | 213 */ |
| 238 MainMessage _messageFromNode(MethodInvocation node, Function extract, | 214 MainMessage _messageFromNode(MethodInvocation node, Function extract, |
| 239 Function setAttribute) { | 215 Function setAttribute) { |
| 240 var message = new MainMessage(); | 216 var message = new MainMessage(); |
| 241 message.name = name; | 217 message.name = name; |
| 242 message.arguments = parameters.parameters.elements.map( | 218 message.arguments = parameters.parameters.elements.map( |
| 243 (x) => x.identifier.name).toList(); | 219 (x) => x.identifier.name).toList(); |
| 244 var arguments = node.argumentList.arguments.elements; | 220 var arguments = node.argumentList.arguments.elements; |
| 245 extract(message, arguments); | 221 extract(message, arguments); |
| 246 | 222 |
| 247 for (NamedExpression namedArgument in arguments.skip(1)) { | 223 for (var namedArgument in arguments.where((x) => x is NamedExpression)) { |
| 248 var name = namedArgument.name.label.name; | 224 var name = namedArgument.name.label.name; |
| 249 var exp = namedArgument.expression; | 225 var exp = namedArgument.expression; |
| 250 var string = exp is SimpleStringLiteral ? exp.value : exp.toString(); | 226 var string = exp is SimpleStringLiteral ? exp.value : exp.toString(); |
| 251 setAttribute(message, name, string); | 227 setAttribute(message, name, string); |
| 252 } | 228 } |
| 253 return message; | 229 return message; |
| 254 } | 230 } |
| 255 | 231 |
| 256 /** | 232 /** |
| 257 * Create a MainMessage from [node] using the name and | 233 * Create a MainMessage from [node] using the name and |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 412 super.visitInterpolationExpression(node); | 388 super.visitInterpolationExpression(node); |
| 413 } | 389 } |
| 414 | 390 |
| 415 visitMethodInvocation(MethodInvocation node) { | 391 visitMethodInvocation(MethodInvocation node) { |
| 416 pieces.add(messageFromMethodInvocation(node)); | 392 pieces.add(messageFromMethodInvocation(node)); |
| 417 super.visitMethodInvocation(node); | 393 super.visitMethodInvocation(node); |
| 418 } | 394 } |
| 419 | 395 |
| 420 /** Return true if [node] matches the pattern for plural or gender message.*/ | 396 /** Return true if [node] matches the pattern for plural or gender message.*/ |
| 421 bool looksLikePluralOrGender(MethodInvocation node) { | 397 bool looksLikePluralOrGender(MethodInvocation node) { |
| 422 if (!["plural", "gender"].contains(node.methodName.name)) return false; | 398 if (!["plural", "gender", "select"].contains(node.methodName.name)) { |
| 399 return false; |
| 400 } |
| 423 if (!(node.target is SimpleIdentifier)) return false; | 401 if (!(node.target is SimpleIdentifier)) return false; |
| 424 SimpleIdentifier target = node.target; | 402 SimpleIdentifier target = node.target; |
| 425 if (target.token.toString() != "Intl") return false; | 403 if (target.token.toString() != "Intl") return false; |
| 426 return true; | 404 return true; |
| 427 } | 405 } |
| 428 | 406 |
| 429 /** | 407 /** |
| 430 * Returns a String describing why the node is invalid, or null if no | 408 * Returns a String describing why the node is invalid, or null if no |
| 431 * reason is found, so it's presumed valid. | 409 * reason is found, so it's presumed valid. |
| 432 */ | 410 */ |
| 433 String checkValidity(MethodInvocation node) { | 411 String checkValidity(MethodInvocation node) { |
| 434 // TODO(alanknight): Add reasonable validity checks. | 412 // TODO(alanknight): Add reasonable validity checks. |
| 435 } | 413 } |
| 436 | 414 |
| 437 /** | 415 /** |
| 438 * Create a MainMessage from [node] using the name and | 416 * Create a MainMessage from [node] using the name and |
| 439 * parameters of the last function/method declaration we encountered | 417 * parameters of the last function/method declaration we encountered |
| 440 * and the parameters to the Intl.message call. | 418 * and the parameters to the Intl.message call. |
| 441 */ | 419 */ |
| 442 Message messageFromMethodInvocation(MethodInvocation node) { | 420 Message messageFromMethodInvocation(MethodInvocation node) { |
| 443 var message; | 421 var message; |
| 444 if (node.methodName.name == "gender") { | 422 switch(node.methodName.name) { |
| 445 message = new Gender(); | 423 case "gender" : message = new Gender(); break; |
| 446 } else if (node.methodName.name == "plural") { | 424 case "plural" : message = new Plural(); break; |
| 447 message = new Plural(); | 425 case "select" : message = new Select(); break; |
| 448 } else { | 426 default: throw new IntlMessageExtractionException( |
| 449 throw new IntlMessageExtractionException("Invalid plural/gender message"); | 427 "Invalid plural/gender/select message"); |
| 450 } | 428 } |
| 451 message.parent = parent; | 429 message.parent = parent; |
| 452 | 430 |
| 453 var arguments = node.argumentList.arguments.elements; | 431 var arguments = message.argumentsOfInterestFor(node); |
| 454 for (var arg in arguments.where((each) => each is NamedExpression)) { | 432 arguments.forEach((key, value) { |
| 455 try { | 433 try { |
| 456 var interpolation = new InterpolationVisitor(message); | 434 var interpolation = new InterpolationVisitor(message); |
| 457 arg.expression.accept(interpolation); | 435 value.accept(interpolation); |
| 458 message[arg.name.label.token.toString()] = interpolation.pieces; | 436 message[key] = interpolation.pieces; |
| 459 } on IntlMessageExtractionException catch (e) { | 437 } on IntlMessageExtractionException catch (e) { |
| 460 message = null; | 438 message = null; |
| 461 var err = new StringBuffer(); | 439 var err = new StringBuffer(); |
| 462 err.write("Error $e"); | 440 err.write("Error $e"); |
| 463 err.write("Processing <$node>"); | 441 err.write("Processing <$node>"); |
| 464 err.write(_reportErrorLocation(node)); | 442 err.write(_reportErrorLocation(node)); |
| 465 print(err); | 443 print(err); |
| 466 warnings.add(err); | 444 warnings.add(err); |
| 467 } | 445 } |
| 468 } | 446 }); |
| 469 var mainArg = node.argumentList.arguments.elements.firstWhere( | 447 var mainArg = node.argumentList.arguments.elements.firstWhere( |
| 470 (each) => each is! NamedExpression); | 448 (each) => each is! NamedExpression); |
| 471 if (mainArg is SimpleStringLiteral) { | 449 if (mainArg is SimpleStringLiteral) { |
| 472 message.mainArgument = mainArg.toString(); | 450 message.mainArgument = mainArg.toString(); |
| 473 } else { | 451 } else { |
| 474 message.mainArgument = mainArg.name; | 452 message.mainArgument = mainArg.name; |
| 475 } | 453 } |
| 476 return message; | 454 return message; |
| 477 } | 455 } |
| 478 } | 456 } |
| 479 | 457 |
| 480 /** | 458 /** |
| 481 * Exception thrown when we cannot process a message properly. | 459 * Exception thrown when we cannot process a message properly. |
| 482 */ | 460 */ |
| 483 class IntlMessageExtractionException implements Exception { | 461 class IntlMessageExtractionException implements Exception { |
| 484 /** | 462 /** |
| 485 * A message describing the error. | 463 * A message describing the error. |
| 486 */ | 464 */ |
| 487 final String message; | 465 final String message; |
| 488 | 466 |
| 489 /** | 467 /** |
| 490 * Creates a new exception with an optional error [message]. | 468 * Creates a new exception with an optional error [message]. |
| 491 */ | 469 */ |
| 492 const IntlMessageExtractionException([this.message = ""]); | 470 const IntlMessageExtractionException([this.message = ""]); |
| 493 | 471 |
| 494 String toString() => "IntlMessageExtractionException: $message"; | 472 String toString() => "IntlMessageExtractionException: $message"; |
| 495 } | 473 } |
| OLD | NEW |