Chromium Code Reviews| 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 print("${messageName.expression} != $name"); | |
|
Emily Fortuna
2013/08/06 19:31:30
debug statement or intended?
Alan Knight
2013/08/06 20:00:26
Oops. Debug statement. Removed.
| |
| 158 return "The 'name' argument for Intl.message must be a simple string " | |
| 159 "literal and match the containing function name."; | |
| 160 } | |
| 135 var hasArgs = namedArguments.any((each) => each.name.label.name == 'args'); | 161 var hasArgs = namedArguments.any((each) => each.name.label.name == 'args'); |
| 136 var hasParameters = !parameters.parameters.isEmpty; | 162 var hasParameters = !parameters.parameters.isEmpty; |
| 137 if (!hasArgs && hasParameters) { | 163 if (!hasArgs && hasParameters) { |
| 138 return "The 'args' argument for Intl.message must be specified"; | 164 return "The 'args' argument for Intl.message must be specified"; |
| 139 } | 165 } |
| 140 return null; | 166 return null; |
| 141 } | 167 } |
| 142 | 168 |
| 143 /** | 169 /** |
| 144 * Record the parameters of the function or method declaration we last | 170 * Record the parameters of the function or method declaration we last |
| 145 * encountered before seeing the Intl.message call. | 171 * encountered before seeing the Intl.message call. |
| 146 */ | 172 */ |
| 147 void visitMethodDeclaration(MethodDeclaration node) { | 173 void visitMethodDeclaration(MethodDeclaration node) { |
| 148 parameters = node.parameters; | 174 parameters = node.parameters; |
| 149 String name = node.name.name; | 175 name = node.name.name; |
| 150 super.visitMethodDeclaration(node); | 176 super.visitMethodDeclaration(node); |
| 151 } | 177 } |
| 152 | 178 |
| 153 /** | 179 /** |
| 154 * Record the parameters of the function or method declaration we last | 180 * Record the parameters of the function or method declaration we last |
| 155 * encountered before seeing the Intl.message call. | 181 * encountered before seeing the Intl.message call. |
| 156 */ | 182 */ |
| 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) { | 183 void visitFunctionDeclaration(FunctionDeclaration node) { |
| 168 parameters = node.functionExpression.parameters; | 184 parameters = node.functionExpression.parameters; |
| 169 name = node.name.name; | 185 name = node.name.name; |
| 170 super.visitFunctionDeclaration(node); | 186 super.visitFunctionDeclaration(node); |
| 171 } | 187 } |
| 172 | 188 |
| 173 /** | 189 /** |
| 174 * Examine method invocations to see if they look like calls to Intl.message. | 190 * 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 | 191 * 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 | 192 * Intl.message(...Intl.plural...) and we don't want to treat the inner |
| 177 * plural as if it was an outermost message. | 193 * plural as if it was an outermost message. |
| 178 */ | 194 */ |
| 179 void visitMethodInvocation(MethodInvocation node) { | 195 void visitMethodInvocation(MethodInvocation node) { |
| 180 if (!addIntlMessage(node)) { | 196 if (!addIntlMessage(node)) { |
| 181 return super.visitMethodInvocation(node); | 197 return super.visitMethodInvocation(node); |
| 182 } | 198 } |
| 183 } | 199 } |
| 184 | 200 |
| 185 /** | 201 /** |
| 186 * Check that the node looks like an Intl.message invocation, and create | 202 * 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 | 203 * the [IntlMessage] object from it and store it in [messages]. Return true |
| 188 * if we successfully extracted a message and should stop looking. Return | 204 * if we successfully extracted a message and should stop looking. Return |
| 189 * false if we didn't, so should continue recursing. | 205 * false if we didn't, so should continue recursing. |
| 190 */ | 206 */ |
| 191 bool addIntlMessage(MethodInvocation node) { | 207 bool addIntlMessage(MethodInvocation node) { |
| 192 if (!looksLikeIntlMessage(node)) return false; | 208 if (!looksLikeIntlMessage(node)) return false; |
| 193 var reason = checkValidity(node); | 209 var reason = checkValidity(node); |
| 194 if (reason != null) { | 210 if (reason != null) { |
| 195 if (!suppressWarnings) { | 211 if (!suppressWarnings) { |
| 196 print("Skipping invalid Intl.message invocation\n <$node>"); | 212 var err = new StringBuffer(); |
| 197 print(" reason: $reason"); | 213 err.write("Skipping invalid Intl.message invocation\n <$node>\n"); |
| 198 _reportErrorLocation(node); | 214 err.write(" reason: $reason\n"); |
| 215 err.write(_reportErrorLocation(node)); | |
| 216 warnings.add(err.toString()); | |
| 217 print(err); | |
| 199 } | 218 } |
| 200 // We found one, but it's not valid. Stop recursing. | 219 // We found one, but it's not valid. Stop recursing. |
| 201 return true; | 220 return true; |
| 202 } | 221 } |
| 203 var message; | 222 var message; |
| 204 if (node.methodName.name == "message") { | 223 if (node.methodName.name == "message") { |
| 205 message = messageFromIntlMessageCall(node); | 224 message = messageFromIntlMessageCall(node); |
| 206 } else { | 225 } else { |
| 207 message = messageFromDirectPluralOrGenderCall(node); | 226 message = messageFromDirectPluralOrGenderCall(node); |
| 208 } | 227 } |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 242 */ | 261 */ |
| 243 MainMessage messageFromIntlMessageCall(MethodInvocation node) { | 262 MainMessage messageFromIntlMessageCall(MethodInvocation node) { |
| 244 | 263 |
| 245 void extractFromIntlCall(MainMessage message, List arguments) { | 264 void extractFromIntlCall(MainMessage message, List arguments) { |
| 246 try { | 265 try { |
| 247 var interpolation = new InterpolationVisitor(message); | 266 var interpolation = new InterpolationVisitor(message); |
| 248 arguments.first.accept(interpolation); | 267 arguments.first.accept(interpolation); |
| 249 message.messagePieces.addAll(interpolation.pieces); | 268 message.messagePieces.addAll(interpolation.pieces); |
| 250 } on IntlMessageExtractionException catch (e) { | 269 } on IntlMessageExtractionException catch (e) { |
| 251 message = null; | 270 message = null; |
| 252 print("Error $e"); | 271 var err = new StringBuffer(); |
| 253 print("Processing <$node>"); | 272 err.write("Error $e\n"); |
| 254 _reportErrorLocation(node); | 273 err.write("Processing <$node>\n"); |
| 274 err.write(_reportErrorLocation(node)); | |
| 275 print(err); | |
| 276 warnings.add(err); | |
| 255 } | 277 } |
| 256 } | 278 } |
| 257 | 279 |
| 258 void setValue(MainMessage message, String fieldName, String fieldValue) { | 280 void setValue(MainMessage message, String fieldName, String fieldValue) { |
| 259 message[fieldName] = fieldValue; | 281 message[fieldName] = fieldValue; |
| 260 } | 282 } |
| 261 | 283 |
| 262 return _messageFromNode(node, extractFromIntlCall, setValue); | 284 return _messageFromNode(node, extractFromIntlCall, setValue); |
| 263 } | 285 } |
| 264 | 286 |
| (...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 430 message.parent = parent; | 452 message.parent = parent; |
| 431 | 453 |
| 432 var arguments = node.argumentList.arguments.elements; | 454 var arguments = node.argumentList.arguments.elements; |
| 433 for (var arg in arguments.where((each) => each is NamedExpression)) { | 455 for (var arg in arguments.where((each) => each is NamedExpression)) { |
| 434 try { | 456 try { |
| 435 var interpolation = new InterpolationVisitor(message); | 457 var interpolation = new InterpolationVisitor(message); |
| 436 arg.expression.accept(interpolation); | 458 arg.expression.accept(interpolation); |
| 437 message[arg.name.label.token.toString()] = interpolation.pieces; | 459 message[arg.name.label.token.toString()] = interpolation.pieces; |
| 438 } on IntlMessageExtractionException catch (e) { | 460 } on IntlMessageExtractionException catch (e) { |
| 439 message = null; | 461 message = null; |
| 440 print("Error $e"); | 462 var err = new StringBuffer(); |
| 441 print("Processing <$node>"); | 463 err.write("Error $e"); |
| 442 _reportErrorLocation(node); | 464 err.write("Processing <$node>"); |
| 465 err.write(_reportErrorLocation(node)); | |
| 466 print(err); | |
| 467 warnings.add(err); | |
| 443 } | 468 } |
| 444 } | 469 } |
| 445 var mainArg = node.argumentList.arguments.elements.firstWhere( | 470 var mainArg = node.argumentList.arguments.elements.firstWhere( |
| 446 (each) => each is! NamedExpression); | 471 (each) => each is! NamedExpression); |
| 447 if (mainArg is SimpleStringLiteral) { | 472 if (mainArg is SimpleStringLiteral) { |
| 448 message.mainArgument = mainArg.toString(); | 473 message.mainArgument = mainArg.toString(); |
| 449 } else { | 474 } else { |
| 450 message.mainArgument = mainArg.name; | 475 message.mainArgument = mainArg.name; |
| 451 } | 476 } |
| 452 return message; | 477 return message; |
| 453 } | 478 } |
| 454 } | 479 } |
| 455 | 480 |
| 456 /** | 481 /** |
| 457 * Exception thrown when we cannot process a message properly. | 482 * Exception thrown when we cannot process a message properly. |
| 458 */ | 483 */ |
| 459 class IntlMessageExtractionException implements Exception { | 484 class IntlMessageExtractionException implements Exception { |
| 460 /** | 485 /** |
| 461 * A message describing the error. | 486 * A message describing the error. |
| 462 */ | 487 */ |
| 463 final String message; | 488 final String message; |
| 464 | 489 |
| 465 /** | 490 /** |
| 466 * Creates a new exception with an optional error [message]. | 491 * Creates a new exception with an optional error [message]. |
| 467 */ | 492 */ |
| 468 const IntlMessageExtractionException([this.message = ""]); | 493 const IntlMessageExtractionException([this.message = ""]); |
| 469 | 494 |
| 470 String toString() => "IntlMessageExtractionException: $message"; | 495 String toString() => "IntlMessageExtractionException: $message"; |
| 471 } | 496 } |
| OLD | NEW |