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

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

Issue 20072002: Allow Intl.plural/gender as the top-level, omitting the Intl.message wrapper and the first layer of… (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') | no next file with comments »
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 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 /** 85 /**
86 * We keep track of the data from the last MethodDeclaration, 86 * We keep track of the data from the last MethodDeclaration,
87 * FunctionDeclaration or FunctionExpression that we saw on the way down, 87 * FunctionDeclaration or FunctionExpression that we saw on the way down,
88 * as that will be the nearest parent of the Intl.message invocation. 88 * as that will be the nearest parent of the Intl.message invocation.
89 */ 89 */
90 FormalParameterList parameters; 90 FormalParameterList parameters;
91 String name; 91 String name;
92 92
93 /** Return true if [node] matches the pattern we expect for Intl.message() */ 93 /** Return true if [node] matches the pattern we expect for Intl.message() */
94 bool looksLikeIntlMessage(MethodInvocation node) { 94 bool looksLikeIntlMessage(MethodInvocation node) {
95 if (node.methodName.name != "message") return false; 95 const validNames = const ["message", "plural", "gender"];
96 if (!validNames.contains(node.methodName.name)) return false;
96 if (!(node.target is SimpleIdentifier)) return false; 97 if (!(node.target is SimpleIdentifier)) return false;
97 SimpleIdentifier target = node.target; 98 SimpleIdentifier target = node.target;
98 if (target.token.toString() != "Intl") return false; 99 if (target.token.toString() != "Intl") return false;
99 return true; 100 return true;
100 } 101 }
101 102
102 /** 103 /**
103 * Returns a String describing why the node is invalid, or null if no 104 * Returns a String describing why the node is invalid, or null if no
104 * reason is found, so it's presumed valid. 105 * reason is found, so it's presumed valid.
105 */ 106 */
106 String checkValidity(MethodInvocation node) { 107 String checkValidity(MethodInvocation node) {
107 // The containing function cannot have named parameters. 108 // The containing function cannot have named parameters.
108 if (parameters.parameters.any((each) => each.kind == ParameterKind.NAMED)) { 109 if (parameters.parameters.any((each) => each.kind == ParameterKind.NAMED)) {
109 return "Named parameters on message functions are not supported."; 110 return "Named parameters on message functions are not supported.";
110 } 111 }
111 var arguments = node.argumentList.arguments; 112 var arguments = node.argumentList.arguments;
112 if (!(arguments.first is StringLiteral)) { 113
113 return "Intl.message messages must be string literals"; 114 if (node.methodName.name == 'message') {
115 if (!(arguments.first is StringLiteral)) {
116 return "Intl.message messages must be string literals";
117 }
114 } 118 }
119
115 var namedArguments = arguments.skip(1); 120 var namedArguments = arguments.skip(1);
116 // This seems unlikely to happen, but make sure all are NamedExpression 121 // This seems unlikely to happen, but make sure all are NamedExpression
117 // before doing the tests below. 122 // before doing the tests below.
118 if (!namedArguments.every((each) => each is NamedExpression)) { 123 if (!namedArguments.every((each) => each is NamedExpression)) {
119 return "Message arguments except the message must be named"; 124 return "Message arguments except the message must be named";
120 } 125 }
121 var notArgs = namedArguments.where( 126 var notArgs = namedArguments.where(
122 (each) => each.name.label.name != 'args'); 127 (each) => each.name.label.name != 'args');
123 var values = notArgs.map((each) => each.expression).toList(); 128 var values = notArgs.map((each) => each.expression).toList();
124 if (!values.every((each) => each is SimpleStringLiteral)) { 129 if (!values.every((each) => each is SimpleStringLiteral)) {
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 * encountered before seeing the Intl.message call. 165 * encountered before seeing the Intl.message call.
161 */ 166 */
162 void visitFunctionDeclaration(FunctionDeclaration node) { 167 void visitFunctionDeclaration(FunctionDeclaration node) {
163 parameters = node.functionExpression.parameters; 168 parameters = node.functionExpression.parameters;
164 name = node.name.name; 169 name = node.name.name;
165 super.visitFunctionDeclaration(node); 170 super.visitFunctionDeclaration(node);
166 } 171 }
167 172
168 /** 173 /**
169 * Examine method invocations to see if they look like calls to Intl.message. 174 * Examine method invocations to see if they look like calls to Intl.message.
175 * If we've found one, stop recursing. This is important because we can have
176 * Intl.message(...Intl.plural...) and we don't want to treat the inner
177 * plural as if it was an outermost message.
170 */ 178 */
171 void visitMethodInvocation(MethodInvocation node) { 179 void visitMethodInvocation(MethodInvocation node) {
172 addIntlMessage(node); 180 if (!addIntlMessage(node)) {
173 return super.visitNode(node); 181 return super.visitMethodInvocation(node);
Alan Knight 2013/07/23 23:24:29 I changed the super call here because it just seem
182 }
174 } 183 }
175 184
176 /** 185 /**
177 * Check that the node looks like an Intl.message invocation, and create 186 * Check that the node looks like an Intl.message invocation, and create
178 * the [IntlMessage] object from it and store it in [messages]. 187 * the [IntlMessage] object from it and store it in [messages].
Emily Fortuna 2013/07/24 18:35:51 can you update the comment here and explain what
Alan Knight 2013/07/24 19:50:10 Done. Which also pointed out that I was returning
179 */ 188 */
180 void addIntlMessage(MethodInvocation node) { 189 bool addIntlMessage(MethodInvocation node) {
181 if (!looksLikeIntlMessage(node)) return; 190 if (!looksLikeIntlMessage(node)) return false;
182 var reason = checkValidity(node); 191 var reason = checkValidity(node);
183 if (reason != null && !suppressWarnings) { 192 if (reason != null) {
Alan Knight 2013/07/23 23:24:29 If suppress warnings was on, this was including th
184 print("Skipping invalid Intl.message invocation\n <$node>"); 193 if (!suppressWarnings) {
185 print(" reason: $reason"); 194 print("Skipping invalid Intl.message invocation\n <$node>");
186 _reportErrorLocation(node); 195 print(" reason: $reason");
187 return; 196 _reportErrorLocation(node);
197 }
198 return false;
188 } 199 }
189 var message = messageFromMethodInvocation(node); 200 var message;
201 if (node.methodName.name == "message") {
202 message = messageFromIntlMessageCall(node);
203 } else {
204 message = messageFromDirectPluralOrGenderCall(node);
205 }
190 if (message != null) messages[message.name] = message; 206 if (message != null) messages[message.name] = message;
207 return true;
191 } 208 }
192 209
193 /** 210 /**
194 * Create an IntlMessage from [node] using the name and 211 * Create an IntlMessage from [node] using the name and
195 * parameters of the last function/method declaration we encountered 212 * parameters of the last function/method declaration we encountered
196 * and the parameters to the Intl.message call. 213 * and the parameters to the Intl.message call.
197 */ 214 */
198 MainMessage messageFromMethodInvocation(MethodInvocation node) { 215 MainMessage messageFromIntlMessageCall(MethodInvocation node) {
199 var message = new MainMessage(); 216 var message = new MainMessage();
200 message.name = name; 217 message.name = name;
201 message.arguments = parameters.parameters.elements.map( 218 message.arguments = parameters.parameters.elements.map(
202 (x) => x.identifier.name).toList(); 219 (x) => x.identifier.name).toList();
203 var arguments = node.argumentList.arguments.elements; 220 var arguments = node.argumentList.arguments.elements;
204 try { 221 try {
205 var interpolation = new InterpolationVisitor(message); 222 var interpolation = new InterpolationVisitor(message);
206 arguments.first.accept(interpolation); 223 arguments.first.accept(interpolation);
207 message.messagePieces.addAll(interpolation.pieces); 224 message.messagePieces.addAll(interpolation.pieces);
208 } on IntlMessageExtractionException catch (e) { 225 } on IntlMessageExtractionException catch (e) {
209 message = null; 226 message = null;
210 print("Error $e"); 227 print("Error $e");
211 print("Processing <$node>"); 228 print("Processing <$node>");
212 _reportErrorLocation(node); 229 _reportErrorLocation(node);
213 } 230 }
214 for (NamedExpression namedArgument in arguments.skip(1)) { 231 for (NamedExpression namedArgument in arguments.skip(1)) {
215 var name = namedArgument.name.label.name; 232 var name = namedArgument.name.label.name;
216 var exp = namedArgument.expression; 233 var exp = namedArgument.expression;
217 var string = exp is SimpleStringLiteral ? exp.value : exp.toString(); 234 var string = exp is SimpleStringLiteral ? exp.value : exp.toString();
218 message[name] = string; 235 message[name] = string;
219 } 236 }
220 return message; 237 return message;
221 } 238 }
239
240 /**
241 * Create an IntlMessage from [node] using the name and
242 * parameters of the last function/method declaration we encountered
243 * and the parameters to the Intl.message call.
244 */
245 MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) {
246 // TODO(alanknight): Refactor this to reduce code duplication.
Emily Fortuna 2013/07/24 18:35:51 why not refactor now? :-)
Alan Knight 2013/07/24 19:50:10 Because I looked a bit and it didn't look easy. Bu
247 var message = new MainMessage();
248 message.name = name;
249 message.arguments = parameters.parameters.elements.map(
250 (x) => x.identifier.name).toList();
251 var arguments = node.argumentList.arguments.elements;
252 var visitor = new PluralAndGenderVisitor(message.messagePieces, message);
253 node.accept(visitor);
254 var pluralOrGender = message.messagePieces.last;
255 for (NamedExpression namedArgument in arguments.skip(1)) {
256 var name = namedArgument.name.label.name;
257 var exp = namedArgument.expression;
258 var string = exp is SimpleStringLiteral ? exp.value : exp.toString();
259 if (["name", "desc", "examples", "args"].contains(name)) {
260 message[name] = string;
261 } else {
262 pluralOrGender[name] = string;
263 }
264 }
265 return message;
266 }
222 } 267 }
223 268
224 /** 269 /**
225 * Given an interpolation, find all of its chunks, validate that they are only 270 * Given an interpolation, find all of its chunks, validate that they are only
226 * simple variable substitutions or else Intl.plural/gender calls, 271 * simple variable substitutions or else Intl.plural/gender calls,
227 * and keep track of the pieces of text so that other parts 272 * and keep track of the pieces of text so that other parts
228 * of the program can deal with the simple string sections and the generated 273 * of the program can deal with the simple string sections and the generated
229 * parts separately. Note that this is a SimpleASTVisitor, so it only 274 * parts separately. Note that this is a SimpleASTVisitor, so it only
230 * traverses one level of children rather than automatically recursing. If we 275 * traverses one level of children rather than automatically recursing. If we
231 * find a plural or gender, which requires recursion, we do it with a separate 276 * find a plural or gender, which requires recursion, we do it with a separate
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
318 // TODO(alanknight): Provide better errors for malformed expressions. 363 // TODO(alanknight): Provide better errors for malformed expressions.
319 if (!looksLikePluralOrGender(node.expression)) return; 364 if (!looksLikePluralOrGender(node.expression)) return;
320 var reason = checkValidity(node.expression); 365 var reason = checkValidity(node.expression);
321 if (reason != null) throw reason; 366 if (reason != null) throw reason;
322 var message = messageFromMethodInvocation(node.expression); 367 var message = messageFromMethodInvocation(node.expression);
323 foundPluralOrGender = true; 368 foundPluralOrGender = true;
324 pieces.add(message); 369 pieces.add(message);
325 super.visitInterpolationExpression(node); 370 super.visitInterpolationExpression(node);
326 } 371 }
327 372
328 /** Return true if [node] matches the pattern we expect for Intl.message() */ 373 visitMethodInvocation(MethodInvocation node) {
374 pieces.add(messageFromMethodInvocation(node));
375 super.visitMethodInvocation(node);
376 }
377
378 /** Return true if [node] matches the pattern for plural or gender message.*/
329 bool looksLikePluralOrGender(MethodInvocation node) { 379 bool looksLikePluralOrGender(MethodInvocation node) {
330 if (!["plural", "gender"].contains(node.methodName.name)) return false; 380 if (!["plural", "gender"].contains(node.methodName.name)) return false;
331 if (!(node.target is SimpleIdentifier)) return false; 381 if (!(node.target is SimpleIdentifier)) return false;
332 SimpleIdentifier target = node.target; 382 SimpleIdentifier target = node.target;
333 if (target.token.toString() != "Intl") return false; 383 if (target.token.toString() != "Intl") return false;
334 return true; 384 return true;
335 } 385 }
336 386
337 /** 387 /**
338 * Returns a String describing why the node is invalid, or null if no 388 * Returns a String describing why the node is invalid, or null if no
339 * reason is found, so it's presumed valid. 389 * reason is found, so it's presumed valid.
340 */ 390 */
341 String checkValidity(MethodInvocation node) { 391 String checkValidity(MethodInvocation node) {
342 // TODO(alanknight): Add reasonable validity checks. 392 // TODO(alanknight): Add reasonable validity checks.
343 } 393 }
344 394
345 /** 395 /**
346 * Create a MainMessage from [node] using the name and 396 * Create a MainMessage from [node] using the name and
347 * parameters of the last function/method declaration we encountered 397 * parameters of the last function/method declaration we encountered
348 * and the parameters to the Intl.message call. 398 * and the parameters to the Intl.message call.
349 */ 399 */
350 messageFromMethodInvocation(MethodInvocation node) { 400 Message messageFromMethodInvocation(MethodInvocation node) {
351 var message; 401 var message;
352 if (node.methodName.name == "gender") { 402 if (node.methodName.name == "gender") {
353 message = new Gender(); 403 message = new Gender();
354 } else if (node.methodName.name == "plural") { 404 } else if (node.methodName.name == "plural") {
355 message = new Plural(); 405 message = new Plural();
356 } else { 406 } else {
357 throw new IntlMessageExtractionException("Invalid plural/gender message"); 407 throw new IntlMessageExtractionException("Invalid plural/gender message");
358 } 408 }
359 message.parent = parent; 409 message.parent = parent;
360 410
(...skipping 30 matching lines...) Expand all
391 */ 441 */
392 final String message; 442 final String message;
393 443
394 /** 444 /**
395 * Creates a new exception with an optional error [message]. 445 * Creates a new exception with an optional error [message].
396 */ 446 */
397 const IntlMessageExtractionException([this.message = ""]); 447 const IntlMessageExtractionException([this.message = ""]);
398 448
399 String toString() => "IntlMessageExtractionException: $message"; 449 String toString() => "IntlMessageExtractionException: $message";
400 } 450 }
OLDNEW
« no previous file with comments | « no previous file | pkg/intl/lib/intl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698