Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(812)

Side by Side Diff: pkg/intl/lib/extract_messages.dart

Issue 22286013: Add support for Intl.select (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/intl/lib/intl.dart » ('j') | pkg/intl/lib/intl.dart » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/intl/lib/intl.dart » ('j') | pkg/intl/lib/intl.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698