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

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: Changes from review Created 7 years, 5 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);
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]. Return true
188 * if we successfully extracted a message and should stop looking. Return
189 * false if we didn't, so should continue recursing.
179 */ 190 */
180 void addIntlMessage(MethodInvocation node) { 191 bool addIntlMessage(MethodInvocation node) {
181 if (!looksLikeIntlMessage(node)) return; 192 if (!looksLikeIntlMessage(node)) return false;
182 var reason = checkValidity(node); 193 var reason = checkValidity(node);
183 if (reason != null && !suppressWarnings) { 194 if (reason != null) {
184 print("Skipping invalid Intl.message invocation\n <$node>"); 195 if (!suppressWarnings) {
185 print(" reason: $reason"); 196 print("Skipping invalid Intl.message invocation\n <$node>");
186 _reportErrorLocation(node); 197 print(" reason: $reason");
187 return; 198 _reportErrorLocation(node);
199 }
200 // We found one, but it's not valid. Stop recursing.
201 return true;
188 } 202 }
189 var message = messageFromMethodInvocation(node); 203 var message;
204 if (node.methodName.name == "message") {
205 message = messageFromIntlMessageCall(node);
206 } else {
207 message = messageFromDirectPluralOrGenderCall(node);
208 }
190 if (message != null) messages[message.name] = message; 209 if (message != null) messages[message.name] = message;
210 return true;
191 } 211 }
192 212
193 /** 213 /**
194 * Create an IntlMessage from [node] using the name and 214 * Create a MainMessage from [node] using the name and
195 * parameters of the last function/method declaration we encountered 215 * parameters of the last function/method declaration we encountered,
196 * and the parameters to the Intl.message call. 216 * and the values we get by calling [extract]. We set those values
217 * by calling [setAttribute]. This is the common parts between
218 * [messageFromIntlMessageCall] and [messageFromDirectPluralOrGenderCall].
197 */ 219 */
198 MainMessage messageFromMethodInvocation(MethodInvocation node) { 220 MainMessage _messageFromNode(MethodInvocation node, Function extract,
221 Function setAttribute) {
199 var message = new MainMessage(); 222 var message = new MainMessage();
200 message.name = name; 223 message.name = name;
201 message.arguments = parameters.parameters.elements.map( 224 message.arguments = parameters.parameters.elements.map(
202 (x) => x.identifier.name).toList(); 225 (x) => x.identifier.name).toList();
203 var arguments = node.argumentList.arguments.elements; 226 var arguments = node.argumentList.arguments.elements;
204 try { 227 extract(message, arguments);
205 var interpolation = new InterpolationVisitor(message); 228
206 arguments.first.accept(interpolation);
207 message.messagePieces.addAll(interpolation.pieces);
208 } on IntlMessageExtractionException catch (e) {
209 message = null;
210 print("Error $e");
211 print("Processing <$node>");
212 _reportErrorLocation(node);
213 }
214 for (NamedExpression namedArgument in arguments.skip(1)) { 229 for (NamedExpression namedArgument in arguments.skip(1)) {
215 var name = namedArgument.name.label.name; 230 var name = namedArgument.name.label.name;
216 var exp = namedArgument.expression; 231 var exp = namedArgument.expression;
217 var string = exp is SimpleStringLiteral ? exp.value : exp.toString(); 232 var string = exp is SimpleStringLiteral ? exp.value : exp.toString();
218 message[name] = string; 233 setAttribute(message, name, string);
219 } 234 }
220 return message; 235 return message;
221 } 236 }
237
238 /**
239 * Create a MainMessage from [node] using the name and
240 * parameters of the last function/method declaration we encountered
241 * and the parameters to the Intl.message call.
242 */
243 MainMessage messageFromIntlMessageCall(MethodInvocation node) {
244
245 void extractFromIntlCall(MainMessage message, List arguments) {
246 try {
247 var interpolation = new InterpolationVisitor(message);
248 arguments.first.accept(interpolation);
249 message.messagePieces.addAll(interpolation.pieces);
250 } on IntlMessageExtractionException catch (e) {
251 message = null;
252 print("Error $e");
253 print("Processing <$node>");
254 _reportErrorLocation(node);
255 }
256 }
257
258 void setValue(MainMessage message, String fieldName, String fieldValue) {
259 message[fieldName] = fieldValue;
260 }
261
262 return _messageFromNode(node, extractFromIntlCall, setValue);
263 }
264
265 /**
266 * Create a MainMessage from [node] using the name and
267 * parameters of the last function/method declaration we encountered
268 * and the parameters to the Intl.plural or Intl.gender call.
269 */
270 MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) {
271 var pluralOrGender;
272
273 void extractFromPluralOrGender(MainMessage message, _) {
274 var visitor = new PluralAndGenderVisitor(message.messagePieces, message);
275 node.accept(visitor);
276 pluralOrGender = message.messagePieces.last;
277 }
278
279 void setAttribute(MainMessage msg, String fieldName, String fieldValue) {
280 if (["name", "desc", "examples", "args"].contains(fieldName)) {
281 msg[fieldName] = fieldValue;
282 } else {
283 pluralOrGender[fieldName] = fieldValue;
284 }
285 }
286 return _messageFromNode(node, extractFromPluralOrGender, setAttribute);
287 }
222 } 288 }
223 289
224 /** 290 /**
225 * Given an interpolation, find all of its chunks, validate that they are only 291 * Given an interpolation, find all of its chunks, validate that they are only
226 * simple variable substitutions or else Intl.plural/gender calls, 292 * simple variable substitutions or else Intl.plural/gender calls,
227 * and keep track of the pieces of text so that other parts 293 * 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 294 * 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 295 * parts separately. Note that this is a SimpleASTVisitor, so it only
230 * traverses one level of children rather than automatically recursing. If we 296 * 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 297 * 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. 384 // TODO(alanknight): Provide better errors for malformed expressions.
319 if (!looksLikePluralOrGender(node.expression)) return; 385 if (!looksLikePluralOrGender(node.expression)) return;
320 var reason = checkValidity(node.expression); 386 var reason = checkValidity(node.expression);
321 if (reason != null) throw reason; 387 if (reason != null) throw reason;
322 var message = messageFromMethodInvocation(node.expression); 388 var message = messageFromMethodInvocation(node.expression);
323 foundPluralOrGender = true; 389 foundPluralOrGender = true;
324 pieces.add(message); 390 pieces.add(message);
325 super.visitInterpolationExpression(node); 391 super.visitInterpolationExpression(node);
326 } 392 }
327 393
328 /** Return true if [node] matches the pattern we expect for Intl.message() */ 394 visitMethodInvocation(MethodInvocation node) {
395 pieces.add(messageFromMethodInvocation(node));
396 super.visitMethodInvocation(node);
397 }
398
399 /** Return true if [node] matches the pattern for plural or gender message.*/
329 bool looksLikePluralOrGender(MethodInvocation node) { 400 bool looksLikePluralOrGender(MethodInvocation node) {
330 if (!["plural", "gender"].contains(node.methodName.name)) return false; 401 if (!["plural", "gender"].contains(node.methodName.name)) return false;
331 if (!(node.target is SimpleIdentifier)) return false; 402 if (!(node.target is SimpleIdentifier)) return false;
332 SimpleIdentifier target = node.target; 403 SimpleIdentifier target = node.target;
333 if (target.token.toString() != "Intl") return false; 404 if (target.token.toString() != "Intl") return false;
334 return true; 405 return true;
335 } 406 }
336 407
337 /** 408 /**
338 * Returns a String describing why the node is invalid, or null if no 409 * Returns a String describing why the node is invalid, or null if no
339 * reason is found, so it's presumed valid. 410 * reason is found, so it's presumed valid.
340 */ 411 */
341 String checkValidity(MethodInvocation node) { 412 String checkValidity(MethodInvocation node) {
342 // TODO(alanknight): Add reasonable validity checks. 413 // TODO(alanknight): Add reasonable validity checks.
343 } 414 }
344 415
345 /** 416 /**
346 * Create a MainMessage from [node] using the name and 417 * Create a MainMessage from [node] using the name and
347 * parameters of the last function/method declaration we encountered 418 * parameters of the last function/method declaration we encountered
348 * and the parameters to the Intl.message call. 419 * and the parameters to the Intl.message call.
349 */ 420 */
350 messageFromMethodInvocation(MethodInvocation node) { 421 Message messageFromMethodInvocation(MethodInvocation node) {
351 var message; 422 var message;
352 if (node.methodName.name == "gender") { 423 if (node.methodName.name == "gender") {
353 message = new Gender(); 424 message = new Gender();
354 } else if (node.methodName.name == "plural") { 425 } else if (node.methodName.name == "plural") {
355 message = new Plural(); 426 message = new Plural();
356 } else { 427 } else {
357 throw new IntlMessageExtractionException("Invalid plural/gender message"); 428 throw new IntlMessageExtractionException("Invalid plural/gender message");
358 } 429 }
359 message.parent = parent; 430 message.parent = parent;
360 431
(...skipping 30 matching lines...) Expand all
391 */ 462 */
392 final String message; 463 final String message;
393 464
394 /** 465 /**
395 * Creates a new exception with an optional error [message]. 466 * Creates a new exception with an optional error [message].
396 */ 467 */
397 const IntlMessageExtractionException([this.message = ""]); 468 const IntlMessageExtractionException([this.message = ""]);
398 469
399 String toString() => "IntlMessageExtractionException: $message"; 470 String toString() => "IntlMessageExtractionException: $message";
400 } 471 }
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