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 29 matching lines...) Expand all Loading... |
40 /** | 40 /** |
41 * This accumulates a list of all warnings/errors we have found. These are | 41 * This accumulates a list of all warnings/errors we have found. These are |
42 * saved as strings right now, so all that can really be done is print and | 42 * saved as strings right now, so all that can really be done is print and |
43 * count them. | 43 * count them. |
44 */ | 44 */ |
45 List<String> warnings = []; | 45 List<String> warnings = []; |
46 | 46 |
47 /** Were there any warnings or errors in extracting messages. */ | 47 /** Were there any warnings or errors in extracting messages. */ |
48 bool get hasWarnings => warnings.isNotEmpty; | 48 bool get hasWarnings => warnings.isNotEmpty; |
49 | 49 |
| 50 /** Are plural and gender expressions required to be at the top level |
| 51 * of an expression, or are they allowed to be embedded in string literals. |
| 52 * |
| 53 * For example, the following expression |
| 54 * 'There are ${Intl.plural(...)} items'. |
| 55 * is legal if [allowEmbeddedPluralsAndGenders] is true, but illegal |
| 56 * if [allowEmbeddedPluralsAndGenders] is false. |
| 57 */ |
| 58 bool allowEmbeddedPluralsAndGenders = true; |
| 59 |
50 /** | 60 /** |
51 * Parse the source of the Dart program file [file] and return a Map from | 61 * Parse the source of the Dart program file [file] and return a Map from |
52 * message names to [IntlMessage] instances. | 62 * message names to [IntlMessage] instances. |
53 */ | 63 */ |
54 Map<String, MainMessage> parseFile(File file) { | 64 Map<String, MainMessage> parseFile(File file) { |
55 try { | 65 try { |
56 _root = parseDartFile(file.path); | 66 _root = parseDartFile(file.path); |
57 } on AnalyzerErrorGroup catch (e) { | 67 } on AnalyzerErrorGroup catch (e) { |
58 print("Error in parsing ${file.path}, no messages extracted."); | 68 print("Error in parsing ${file.path}, no messages extracted."); |
59 print(" $e"); | 69 print(" $e"); |
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
221 * by calling [setAttribute]. This is the common parts between | 231 * by calling [setAttribute]. This is the common parts between |
222 * [messageFromIntlMessageCall] and [messageFromDirectPluralOrGenderCall]. | 232 * [messageFromIntlMessageCall] and [messageFromDirectPluralOrGenderCall]. |
223 */ | 233 */ |
224 MainMessage _messageFromNode(MethodInvocation node, Function extract, | 234 MainMessage _messageFromNode(MethodInvocation node, Function extract, |
225 Function setAttribute) { | 235 Function setAttribute) { |
226 var message = new MainMessage(); | 236 var message = new MainMessage(); |
227 message.name = name; | 237 message.name = name; |
228 message.arguments = parameters.parameters.map( | 238 message.arguments = parameters.parameters.map( |
229 (x) => x.identifier.name).toList(); | 239 (x) => x.identifier.name).toList(); |
230 var arguments = node.argumentList.arguments; | 240 var arguments = node.argumentList.arguments; |
231 extract(message, arguments); | 241 var extractionResult = extract(message, arguments); |
| 242 if (extractionResult == null) return null; |
232 | 243 |
233 for (var namedArgument in arguments.where((x) => x is NamedExpression)) { | 244 for (var namedArgument in arguments.where((x) => x is NamedExpression)) { |
234 var name = namedArgument.name.label.name; | 245 var name = namedArgument.name.label.name; |
235 var exp = namedArgument.expression; | 246 var exp = namedArgument.expression; |
236 var evaluator = new ConstantEvaluator(); | 247 var evaluator = new ConstantEvaluator(); |
237 var basicValue = exp.accept(evaluator); | 248 var basicValue = exp.accept(evaluator); |
238 var value = basicValue == ConstantEvaluator.NOT_A_CONSTANT ? | 249 var value = basicValue == ConstantEvaluator.NOT_A_CONSTANT ? |
239 exp.toString() : basicValue; | 250 exp.toString() : basicValue; |
240 setAttribute(message, name, value); | 251 setAttribute(message, name, value); |
241 } | 252 } |
242 return message; | 253 return message; |
243 } | 254 } |
244 | 255 |
245 /** | 256 /** |
246 * Create a MainMessage from [node] using the name and | 257 * Create a MainMessage from [node] using the name and |
247 * parameters of the last function/method declaration we encountered | 258 * parameters of the last function/method declaration we encountered |
248 * and the parameters to the Intl.message call. | 259 * and the parameters to the Intl.message call. |
249 */ | 260 */ |
250 MainMessage messageFromIntlMessageCall(MethodInvocation node) { | 261 MainMessage messageFromIntlMessageCall(MethodInvocation node) { |
251 | 262 |
252 void extractFromIntlCall(MainMessage message, List arguments) { | 263 MainMessage extractFromIntlCall(MainMessage message, List arguments) { |
253 try { | 264 try { |
254 var interpolation = new InterpolationVisitor(message); | 265 var interpolation = new InterpolationVisitor(message); |
255 arguments.first.accept(interpolation); | 266 arguments.first.accept(interpolation); |
| 267 if (interpolation.pieces.any((x) => x is Plural || x is Gender) && |
| 268 !allowEmbeddedPluralsAndGenders) { |
| 269 if (interpolation.pieces.any((x) => x is String && x.isNotEmpty)) { |
| 270 throw new IntlMessageExtractionException( |
| 271 "Plural and gender expressions must be at the top level, " |
| 272 "they cannot be embedded in larger string literals.\n" |
| 273 "Error at $node"); |
| 274 } |
| 275 } |
256 message.messagePieces.addAll(interpolation.pieces); | 276 message.messagePieces.addAll(interpolation.pieces); |
257 } on IntlMessageExtractionException catch (e) { | 277 } on IntlMessageExtractionException catch (e) { |
258 message = null; | 278 message = null; |
259 var err = new StringBuffer() | 279 var err = new StringBuffer() |
260 ..writeAll(["Error ", e, "\nProcessing <", node, ">\n"]) | 280 ..writeAll(["Error ", e, "\nProcessing <", node, ">\n"]) |
261 ..write(_reportErrorLocation(node)); | 281 ..write(_reportErrorLocation(node)); |
262 print(err); | 282 print(err); |
263 warnings.add(err); | 283 warnings.add(err.toString()); |
264 } | 284 } |
| 285 return message; // Because we may have set it to null on an error. |
265 } | 286 } |
266 | 287 |
267 void setValue(MainMessage message, String fieldName, Object fieldValue) { | 288 void setValue(MainMessage message, String fieldName, Object fieldValue) { |
268 message[fieldName] = fieldValue; | 289 message[fieldName] = fieldValue; |
269 } | 290 } |
270 | 291 |
271 return _messageFromNode(node, extractFromIntlCall, setValue); | 292 return _messageFromNode(node, extractFromIntlCall, setValue); |
272 } | 293 } |
273 | 294 |
274 /** | 295 /** |
275 * Create a MainMessage from [node] using the name and | 296 * Create a MainMessage from [node] using the name and |
276 * parameters of the last function/method declaration we encountered | 297 * parameters of the last function/method declaration we encountered |
277 * and the parameters to the Intl.plural or Intl.gender call. | 298 * and the parameters to the Intl.plural or Intl.gender call. |
278 */ | 299 */ |
279 MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) { | 300 MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) { |
280 var pluralOrGender; | 301 var pluralOrGender; |
281 | 302 |
282 void extractFromPluralOrGender(MainMessage message, _) { | 303 MainMessage extractFromPluralOrGender(MainMessage message, _) { |
283 var visitor = new PluralAndGenderVisitor(message.messagePieces, message); | 304 var visitor = new PluralAndGenderVisitor(message.messagePieces, message); |
284 node.accept(visitor); | 305 node.accept(visitor); |
285 pluralOrGender = message.messagePieces.last; | 306 pluralOrGender = message.messagePieces.last; |
| 307 return message; |
286 } | 308 } |
287 | 309 |
288 void setAttribute(MainMessage msg, String fieldName, String fieldValue) { | 310 void setAttribute(MainMessage msg, String fieldName, String fieldValue) { |
289 if (msg.attributeNames.contains(fieldName)) { | 311 if (msg.attributeNames.contains(fieldName)) { |
290 msg[fieldName] = fieldValue; | 312 msg[fieldName] = fieldValue; |
291 } | 313 } |
292 } | 314 } |
293 return _messageFromNode(node, extractFromPluralOrGender, setAttribute); | 315 return _messageFromNode(node, extractFromPluralOrGender, setAttribute); |
294 } | 316 } |
295 } | 317 } |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
417 * Returns a String describing why the node is invalid, or null if no | 439 * Returns a String describing why the node is invalid, or null if no |
418 * reason is found, so it's presumed valid. | 440 * reason is found, so it's presumed valid. |
419 */ | 441 */ |
420 String checkValidity(MethodInvocation node) { | 442 String checkValidity(MethodInvocation node) { |
421 // TODO(alanknight): Add reasonable validity checks. | 443 // TODO(alanknight): Add reasonable validity checks. |
422 return null; | 444 return null; |
423 } | 445 } |
424 | 446 |
425 /** | 447 /** |
426 * Create a MainMessage from [node] using the name and | 448 * Create a MainMessage from [node] using the name and |
427 * parameters of the last function/method declaration we encountered
e | 449 * parameters of the last function/method declaration we encountered |
428 * and the parameters to the Intl.message call. | 450 * and the parameters to the Intl.message call. |
429 */ | 451 */ |
430 Message messageFromMethodInvocation(MethodInvocation node) { | 452 Message messageFromMethodInvocation(MethodInvocation node) { |
431 var message; | 453 var message; |
432 switch(node.methodName.name) { | 454 switch(node.methodName.name) { |
433 case "gender" : message = new Gender(); break; | 455 case "gender" : message = new Gender(); break; |
434 case "plural" : message = new Plural(); break; | 456 case "plural" : message = new Plural(); break; |
435 case "select" : message = new Select(); break; | 457 case "select" : message = new Select(); break; |
436 default: throw new IntlMessageExtractionException( | 458 default: throw new IntlMessageExtractionException( |
437 "Invalid plural/gender/select message"); | 459 "Invalid plural/gender/select message"); |
438 } | 460 } |
439 message.parent = parent; | 461 message.parent = parent; |
440 | 462 |
441 var arguments = message.argumentsOfInterestFor(node); | 463 var arguments = message.argumentsOfInterestFor(node); |
442 arguments.forEach((key, value) { | 464 arguments.forEach((key, value) { |
443 try { | 465 try { |
444 var interpolation = new InterpolationVisitor(message); | 466 var interpolation = new InterpolationVisitor(message); |
445 value.accept(interpolation); | 467 value.accept(interpolation); |
446 message[key] = interpolation.pieces; | 468 message[key] = interpolation.pieces; |
447 } on IntlMessageExtractionException catch (e) { | 469 } on IntlMessageExtractionException catch (e) { |
448 message = null; | 470 message = null; |
449 var err = new StringBuffer() | 471 var err = new StringBuffer() |
450 ..writeAll(["Error ", e, "\nProcessing <", node, ">"]) | 472 ..writeAll(["Error ", e, "\nProcessing <", node, ">"]) |
451 ..write(_reportErrorLocation(node)); | 473 ..write(_reportErrorLocation(node)); |
452 print(err); | 474 print(err); |
453 warnings.add(err); | 475 warnings.add(err.toString()); |
454 } | 476 } |
455 }); | 477 }); |
456 var mainArg = node.argumentList.arguments.firstWhere( | 478 var mainArg = node.argumentList.arguments.firstWhere( |
457 (each) => each is! NamedExpression); | 479 (each) => each is! NamedExpression); |
458 if (mainArg is SimpleStringLiteral) { | 480 if (mainArg is SimpleStringLiteral) { |
459 message.mainArgument = mainArg.toString(); | 481 message.mainArgument = mainArg.toString(); |
460 } else { | 482 } else { |
461 message.mainArgument = mainArg.name; | 483 message.mainArgument = mainArg.name; |
462 } | 484 } |
463 return message; | 485 return message; |
464 } | 486 } |
465 } | 487 } |
466 | 488 |
467 /** | 489 /** |
468 * Exception thrown when we cannot process a message properly. | 490 * Exception thrown when we cannot process a message properly. |
469 */ | 491 */ |
470 class IntlMessageExtractionException implements Exception { | 492 class IntlMessageExtractionException implements Exception { |
471 /** | 493 /** |
472 * A message describing the error. | 494 * A message describing the error. |
473 */ | 495 */ |
474 final String message; | 496 final String message; |
475 | 497 |
476 /** | 498 /** |
477 * Creates a new exception with an optional error [message]. | 499 * Creates a new exception with an optional error [message]. |
478 */ | 500 */ |
479 const IntlMessageExtractionException([this.message = ""]); | 501 const IntlMessageExtractionException([this.message = ""]); |
480 | 502 |
481 String toString() => "IntlMessageExtractionException: $message"; | 503 String toString() => "IntlMessageExtractionException: $message"; |
482 } | 504 } |
OLD | NEW |