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 15 matching lines...) Expand all Loading... |
26 import 'package:analyzer_experimental/analyzer.dart'; | 26 import 'package:analyzer_experimental/analyzer.dart'; |
27 import 'package:intl/src/intl_message.dart'; | 27 import 'package:intl/src/intl_message.dart'; |
28 | 28 |
29 /** | 29 /** |
30 * If this is true, print warnings for skipped messages. Otherwise, warnings | 30 * If this is true, print warnings for skipped messages. Otherwise, warnings |
31 * are suppressed. | 31 * are suppressed. |
32 */ | 32 */ |
33 bool suppressWarnings = false; | 33 bool suppressWarnings = false; |
34 | 34 |
35 /** | 35 /** |
| 36 * If this is true, then treat all warnings as errors. |
| 37 */ |
| 38 bool warningsAreErrors = false; |
| 39 |
| 40 /** |
| 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 |
| 43 * count them. |
| 44 */ |
| 45 List<String> warnings = []; |
| 46 |
| 47 /** Were there any warnings or errors in extracting messages. */ |
| 48 bool get hasWarnings => warnings.isNotEmpty; |
| 49 |
| 50 /** |
36 * Parse the source of the Dart program file [file] and return a Map from | 51 * Parse the source of the Dart program file [file] and return a Map from |
37 * message names to [IntlMessage] instances. | 52 * message names to [IntlMessage] instances. |
38 */ | 53 */ |
39 Map<String, MainMessage> parseFile(File file) { | 54 Map<String, MainMessage> parseFile(File file) { |
40 _root = parseDartFile(file.path); | 55 _root = parseDartFile(file.path); |
41 _origin = file.path; | 56 _origin = file.path; |
42 var visitor = new MessageFindingVisitor(); | 57 var visitor = new MessageFindingVisitor(); |
43 _root.accept(visitor); | 58 _root.accept(visitor); |
44 return visitor.messages; | 59 return visitor.messages; |
45 } | 60 } |
46 | 61 |
47 /** | 62 /** |
48 * The root of the compilation unit, and the first node we visit. We hold | 63 * The root of the compilation unit, and the first node we visit. We hold |
49 * on to this for error reporting, as it can give us line numbers of other | 64 * on to this for error reporting, as it can give us line numbers of other |
50 * nodes. | 65 * nodes. |
51 */ | 66 */ |
52 CompilationUnit _root; | 67 CompilationUnit _root; |
53 | 68 |
54 /** | 69 /** |
55 * An arbitrary string describing where the source code came from. Most | 70 * An arbitrary string describing where the source code came from. Most |
56 * obviously, this could be a file path. We use this when reporting | 71 * obviously, this could be a file path. We use this when reporting |
57 * invalid messages. | 72 * invalid messages. |
58 */ | 73 */ |
59 String _origin; | 74 String _origin; |
60 | 75 |
61 void _reportErrorLocation(ASTNode node) { | 76 String _reportErrorLocation(ASTNode node) { |
62 if (_origin != null) print(" from $_origin"); | 77 var result = new StringBuffer(); |
| 78 if (_origin != null) result.write(" from $_origin"); |
63 var info = _root.lineInfo; | 79 var info = _root.lineInfo; |
64 if (info != null) { | 80 if (info != null) { |
65 var line = info.getLocation(node.offset); | 81 var line = info.getLocation(node.offset); |
66 print(" line: ${line.lineNumber}, column: ${line.columnNumber}"); | 82 result.write(" line: ${line.lineNumber}, column: ${line.columnNumber}"); |
67 } | 83 } |
| 84 return result.toString(); |
68 } | 85 } |
69 | 86 |
70 /** | 87 /** |
71 * This visits the program source nodes looking for Intl.message uses | 88 * This visits the program source nodes looking for Intl.message uses |
72 * that conform to its pattern and then creating the corresponding | 89 * that conform to its pattern and then creating the corresponding |
73 * IntlMessage objects. We have to find both the enclosing function, and | 90 * IntlMessage objects. We have to find both the enclosing function, and |
74 * the Intl.message invocation. | 91 * the Intl.message invocation. |
75 */ | 92 */ |
76 class MessageFindingVisitor extends GeneralizingASTVisitor { | 93 class MessageFindingVisitor extends GeneralizingASTVisitor { |
77 | 94 |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
122 // before doing the tests below. | 139 // before doing the tests below. |
123 if (!namedArguments.every((each) => each is NamedExpression)) { | 140 if (!namedArguments.every((each) => each is NamedExpression)) { |
124 return "Message arguments except the message must be named"; | 141 return "Message arguments except the message must be named"; |
125 } | 142 } |
126 var notArgs = namedArguments.where( | 143 var notArgs = namedArguments.where( |
127 (each) => each.name.label.name != 'args'); | 144 (each) => each.name.label.name != 'args'); |
128 var values = notArgs.map((each) => each.expression).toList(); | 145 var values = notArgs.map((each) => each.expression).toList(); |
129 if (!values.every((each) => each is SimpleStringLiteral)) { | 146 if (!values.every((each) => each is SimpleStringLiteral)) { |
130 "Intl.message arguments must be simple string literals"; | 147 "Intl.message arguments must be simple string literals"; |
131 } | 148 } |
132 if (!notArgs.any((each) => each.name.label.name == 'name')) { | 149 var messageName = notArgs.firstWhere( |
| 150 (eachArg) => eachArg.name.label.name == 'name', |
| 151 orElse: () => null); |
| 152 if (messageName == null) { |
133 return "The 'name' argument for Intl.message must be specified"; | 153 return "The 'name' argument for Intl.message must be specified"; |
134 } | 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 } |
135 var hasArgs = namedArguments.any((each) => each.name.label.name == 'args'); | 160 var hasArgs = namedArguments.any((each) => each.name.label.name == 'args'); |
136 var hasParameters = !parameters.parameters.isEmpty; | 161 var hasParameters = !parameters.parameters.isEmpty; |
137 if (!hasArgs && hasParameters) { | 162 if (!hasArgs && hasParameters) { |
138 return "The 'args' argument for Intl.message must be specified"; | 163 return "The 'args' argument for Intl.message must be specified"; |
139 } | 164 } |
140 return null; | 165 return null; |
141 } | 166 } |
142 | 167 |
143 /** | 168 /** |
144 * Record the parameters of the function or method declaration we last | 169 * Record the parameters of the function or method declaration we last |
145 * encountered before seeing the Intl.message call. | 170 * encountered before seeing the Intl.message call. |
146 */ | 171 */ |
147 void visitMethodDeclaration(MethodDeclaration node) { | 172 void visitMethodDeclaration(MethodDeclaration node) { |
148 parameters = node.parameters; | 173 parameters = node.parameters; |
149 String name = node.name.name; | 174 name = node.name.name; |
150 super.visitMethodDeclaration(node); | 175 super.visitMethodDeclaration(node); |
151 } | 176 } |
152 | 177 |
153 /** | 178 /** |
154 * Record the parameters of the function or method declaration we last | 179 * Record the parameters of the function or method declaration we last |
155 * encountered before seeing the Intl.message call. | 180 * encountered before seeing the Intl.message call. |
156 */ | 181 */ |
157 void visitFunctionExpression(FunctionExpression node) { | |
158 parameters = node.parameters; | |
159 name = null; | |
160 super.visitFunctionExpression(node); | |
161 } | |
162 | |
163 /** | |
164 * Record the parameters of the function or method declaration we last | |
165 * encountered before seeing the Intl.message call. | |
166 */ | |
167 void visitFunctionDeclaration(FunctionDeclaration node) { | 182 void visitFunctionDeclaration(FunctionDeclaration node) { |
168 parameters = node.functionExpression.parameters; | 183 parameters = node.functionExpression.parameters; |
169 name = node.name.name; | 184 name = node.name.name; |
170 super.visitFunctionDeclaration(node); | 185 super.visitFunctionDeclaration(node); |
171 } | 186 } |
172 | 187 |
173 /** | 188 /** |
174 * Examine method invocations to see if they look like calls to Intl.message. | 189 * 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 | 190 * 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 | 191 * Intl.message(...Intl.plural...) and we don't want to treat the inner |
177 * plural as if it was an outermost message. | 192 * plural as if it was an outermost message. |
178 */ | 193 */ |
179 void visitMethodInvocation(MethodInvocation node) { | 194 void visitMethodInvocation(MethodInvocation node) { |
180 if (!addIntlMessage(node)) { | 195 if (!addIntlMessage(node)) { |
181 return super.visitMethodInvocation(node); | 196 return super.visitMethodInvocation(node); |
182 } | 197 } |
183 } | 198 } |
184 | 199 |
185 /** | 200 /** |
186 * Check that the node looks like an Intl.message invocation, and create | 201 * Check that the node looks like an Intl.message invocation, and create |
187 * the [IntlMessage] object from it and store it in [messages]. Return true | 202 * the [IntlMessage] object from it and store it in [messages]. Return true |
188 * if we successfully extracted a message and should stop looking. Return | 203 * if we successfully extracted a message and should stop looking. Return |
189 * false if we didn't, so should continue recursing. | 204 * false if we didn't, so should continue recursing. |
190 */ | 205 */ |
191 bool addIntlMessage(MethodInvocation node) { | 206 bool addIntlMessage(MethodInvocation node) { |
192 if (!looksLikeIntlMessage(node)) return false; | 207 if (!looksLikeIntlMessage(node)) return false; |
193 var reason = checkValidity(node); | 208 var reason = checkValidity(node); |
194 if (reason != null) { | 209 if (reason != null) { |
195 if (!suppressWarnings) { | 210 if (!suppressWarnings) { |
196 print("Skipping invalid Intl.message invocation\n <$node>"); | 211 var err = new StringBuffer(); |
197 print(" reason: $reason"); | 212 err.write("Skipping invalid Intl.message invocation\n <$node>\n"); |
198 _reportErrorLocation(node); | 213 err.write(" reason: $reason\n"); |
| 214 err.write(_reportErrorLocation(node)); |
| 215 warnings.add(err.toString()); |
| 216 print(err); |
199 } | 217 } |
200 // We found one, but it's not valid. Stop recursing. | 218 // We found one, but it's not valid. Stop recursing. |
201 return true; | 219 return true; |
202 } | 220 } |
203 var message; | 221 var message; |
204 if (node.methodName.name == "message") { | 222 if (node.methodName.name == "message") { |
205 message = messageFromIntlMessageCall(node); | 223 message = messageFromIntlMessageCall(node); |
206 } else { | 224 } else { |
207 message = messageFromDirectPluralOrGenderCall(node); | 225 message = messageFromDirectPluralOrGenderCall(node); |
208 } | 226 } |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
242 */ | 260 */ |
243 MainMessage messageFromIntlMessageCall(MethodInvocation node) { | 261 MainMessage messageFromIntlMessageCall(MethodInvocation node) { |
244 | 262 |
245 void extractFromIntlCall(MainMessage message, List arguments) { | 263 void extractFromIntlCall(MainMessage message, List arguments) { |
246 try { | 264 try { |
247 var interpolation = new InterpolationVisitor(message); | 265 var interpolation = new InterpolationVisitor(message); |
248 arguments.first.accept(interpolation); | 266 arguments.first.accept(interpolation); |
249 message.messagePieces.addAll(interpolation.pieces); | 267 message.messagePieces.addAll(interpolation.pieces); |
250 } on IntlMessageExtractionException catch (e) { | 268 } on IntlMessageExtractionException catch (e) { |
251 message = null; | 269 message = null; |
252 print("Error $e"); | 270 var err = new StringBuffer(); |
253 print("Processing <$node>"); | 271 err.write("Error $e\n"); |
254 _reportErrorLocation(node); | 272 err.write("Processing <$node>\n"); |
| 273 err.write(_reportErrorLocation(node)); |
| 274 print(err); |
| 275 warnings.add(err); |
255 } | 276 } |
256 } | 277 } |
257 | 278 |
258 void setValue(MainMessage message, String fieldName, String fieldValue) { | 279 void setValue(MainMessage message, String fieldName, String fieldValue) { |
259 message[fieldName] = fieldValue; | 280 message[fieldName] = fieldValue; |
260 } | 281 } |
261 | 282 |
262 return _messageFromNode(node, extractFromIntlCall, setValue); | 283 return _messageFromNode(node, extractFromIntlCall, setValue); |
263 } | 284 } |
264 | 285 |
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 message.parent = parent; | 451 message.parent = parent; |
431 | 452 |
432 var arguments = node.argumentList.arguments.elements; | 453 var arguments = node.argumentList.arguments.elements; |
433 for (var arg in arguments.where((each) => each is NamedExpression)) { | 454 for (var arg in arguments.where((each) => each is NamedExpression)) { |
434 try { | 455 try { |
435 var interpolation = new InterpolationVisitor(message); | 456 var interpolation = new InterpolationVisitor(message); |
436 arg.expression.accept(interpolation); | 457 arg.expression.accept(interpolation); |
437 message[arg.name.label.token.toString()] = interpolation.pieces; | 458 message[arg.name.label.token.toString()] = interpolation.pieces; |
438 } on IntlMessageExtractionException catch (e) { | 459 } on IntlMessageExtractionException catch (e) { |
439 message = null; | 460 message = null; |
440 print("Error $e"); | 461 var err = new StringBuffer(); |
441 print("Processing <$node>"); | 462 err.write("Error $e"); |
442 _reportErrorLocation(node); | 463 err.write("Processing <$node>"); |
| 464 err.write(_reportErrorLocation(node)); |
| 465 print(err); |
| 466 warnings.add(err); |
443 } | 467 } |
444 } | 468 } |
445 var mainArg = node.argumentList.arguments.elements.firstWhere( | 469 var mainArg = node.argumentList.arguments.elements.firstWhere( |
446 (each) => each is! NamedExpression); | 470 (each) => each is! NamedExpression); |
447 if (mainArg is SimpleStringLiteral) { | 471 if (mainArg is SimpleStringLiteral) { |
448 message.mainArgument = mainArg.toString(); | 472 message.mainArgument = mainArg.toString(); |
449 } else { | 473 } else { |
450 message.mainArgument = mainArg.name; | 474 message.mainArgument = mainArg.name; |
451 } | 475 } |
452 return message; | 476 return message; |
453 } | 477 } |
454 } | 478 } |
455 | 479 |
456 /** | 480 /** |
457 * Exception thrown when we cannot process a message properly. | 481 * Exception thrown when we cannot process a message properly. |
458 */ | 482 */ |
459 class IntlMessageExtractionException implements Exception { | 483 class IntlMessageExtractionException implements Exception { |
460 /** | 484 /** |
461 * A message describing the error. | 485 * A message describing the error. |
462 */ | 486 */ |
463 final String message; | 487 final String message; |
464 | 488 |
465 /** | 489 /** |
466 * Creates a new exception with an optional error [message]. | 490 * Creates a new exception with an optional error [message]. |
467 */ | 491 */ |
468 const IntlMessageExtractionException([this.message = ""]); | 492 const IntlMessageExtractionException([this.message = ""]); |
469 | 493 |
470 String toString() => "IntlMessageExtractionException: $message"; | 494 String toString() => "IntlMessageExtractionException: $message"; |
471 } | 495 } |
OLD | NEW |