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

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

Issue 22392004: Improvements to warnings on message extraction. (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
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 15 matching lines...) Expand all
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698