OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 part of js_ast; | 5 part of js_ast; |
6 | 6 |
7 | 7 |
8 class JavaScriptPrintingOptions { | 8 class JavaScriptPrintingOptions { |
9 final bool shouldCompressOutput; | 9 final bool shouldCompressOutput; |
10 final bool minifyLocalVariables; | 10 final bool minifyLocalVariables; |
11 final bool preferSemicolonToNewlineInMinifiedOutput; | 11 final bool preferSemicolonToNewlineInMinifiedOutput; |
12 final bool avoidKeywordsInIdentifiers; | 12 |
| 13 |
| 14 /// True to allow keywords in properties, such as `obj.var` or `obj.function` |
| 15 /// Modern JS engines support this. |
| 16 final bool allowKeywordsInProperties; |
13 | 17 |
14 JavaScriptPrintingOptions( | 18 JavaScriptPrintingOptions( |
15 {this.shouldCompressOutput: false, | 19 {this.shouldCompressOutput: false, |
16 this.minifyLocalVariables: false, | 20 this.minifyLocalVariables: false, |
17 this.preferSemicolonToNewlineInMinifiedOutput: false, | 21 this.preferSemicolonToNewlineInMinifiedOutput: false, |
18 this.avoidKeywordsInIdentifiers: false}); | 22 this.allowKeywordsInProperties: false}); |
19 } | 23 } |
20 | 24 |
21 | 25 |
22 /// An environment in which JavaScript printing is done. Provides emitting of | 26 /// An environment in which JavaScript printing is done. Provides emitting of |
23 /// text and pre- and post-visit callbacks. | 27 /// text and pre- and post-visit callbacks. |
24 abstract class JavaScriptPrintingContext { | 28 abstract class JavaScriptPrintingContext { |
25 /// Signals an error. This should happen only for serious internal errors. | 29 /// Signals an error. This should happen only for serious internal errors. |
26 void error(String message) { throw message; } | 30 void error(String message) { throw message; } |
27 | 31 |
28 /// Adds [string] to the output. | 32 /// Adds [string] to the output. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
61 | 65 |
62 // The current indentation level. | 66 // The current indentation level. |
63 int _indentLevel = 0; | 67 int _indentLevel = 0; |
64 // A cache of all indentation strings used so far. | 68 // A cache of all indentation strings used so far. |
65 List<String> _indentList = <String>[""]; | 69 List<String> _indentList = <String>[""]; |
66 | 70 |
67 static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); | 71 static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); |
68 static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); | 72 static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); |
69 | 73 |
70 Printer(JavaScriptPrintingOptions options, | 74 Printer(JavaScriptPrintingOptions options, |
71 JavaScriptPrintingContext context) | 75 JavaScriptPrintingContext context, |
| 76 {LocalNamer localNamer}) |
72 : options = options, | 77 : options = options, |
73 context = context, | 78 context = context, |
74 shouldCompressOutput = options.shouldCompressOutput, | 79 shouldCompressOutput = options.shouldCompressOutput, |
75 danglingElseVisitor = new DanglingElseVisitor(context), | 80 danglingElseVisitor = new DanglingElseVisitor(context), |
76 localNamer = determineRenamer(options.shouldCompressOutput, | 81 localNamer = determineRenamer(localNamer, options); |
77 options.minifyLocalVariables); | |
78 | 82 |
79 static LocalNamer determineRenamer(bool shouldCompressOutput, | 83 static LocalNamer determineRenamer(LocalNamer localNamer, |
80 bool allowVariableMinification) { | 84 JavaScriptPrintingOptions options) { |
81 return (shouldCompressOutput && allowVariableMinification) | 85 if (localNamer != null) return localNamer; |
| 86 return (options.shouldCompressOutput && options.minifyLocalVariables) |
82 ? new MinifyRenamer() : new IdentityNamer(); | 87 ? new MinifyRenamer() : new IdentityNamer(); |
83 } | 88 } |
84 | 89 |
85 | 90 |
86 // The current indentation string. | 91 // The current indentation string. |
87 String get indentation { | 92 String get indentation { |
88 // Lazily add new indentation strings as required. | 93 // Lazily add new indentation strings as required. |
89 while (_indentList.length <= _indentLevel) { | 94 while (_indentList.length <= _indentLevel) { |
90 _indentList.add(_indentList.last + " "); | 95 _indentList.add(_indentList.last + " "); |
91 } | 96 } |
(...skipping 679 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
771 } | 776 } |
772 | 777 |
773 visitThis(This node) { | 778 visitThis(This node) { |
774 out("this"); | 779 out("this"); |
775 } | 780 } |
776 | 781 |
777 visitSuper(Super node) { | 782 visitSuper(Super node) { |
778 out("super"); | 783 out("super"); |
779 } | 784 } |
780 | 785 |
781 visitIdentifier(Identifier param) { | 786 visitIdentifier(Identifier node) { |
782 out(localNamer.getName(param.name)); | 787 out(localNamer.getName(node)); |
783 } | 788 } |
784 | 789 |
785 bool isDigit(int charCode) { | 790 bool isDigit(int charCode) { |
786 return charCodes.$0 <= charCode && charCode <= charCodes.$9; | 791 return charCodes.$0 <= charCode && charCode <= charCodes.$9; |
787 } | 792 } |
788 | 793 |
789 bool isValidJavaScriptId(String field) { | 794 bool isValidJavaScriptId(String field) { |
790 if (field.length < 3) return false; | 795 if (field.length < 3) return false; |
791 // Ignore the leading and trailing string-delimiter. | 796 // Ignore the leading and trailing string-delimiter. |
792 for (int i = 1; i < field.length - 1; i++) { | 797 for (int i = 1; i < field.length - 1; i++) { |
793 // TODO(floitsch): allow more characters. | 798 // TODO(floitsch): allow more characters. |
794 int charCode = field.codeUnitAt(i); | 799 int charCode = field.codeUnitAt(i); |
795 if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || | 800 if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || |
796 charCodes.$A <= charCode && charCode <= charCodes.$Z || | 801 charCodes.$A <= charCode && charCode <= charCodes.$Z || |
797 charCode == charCodes.$$ || | 802 charCode == charCodes.$$ || |
798 charCode == charCodes.$_ || | 803 charCode == charCodes.$_ || |
799 i != 1 && isDigit(charCode))) { | 804 i != 1 && isDigit(charCode))) { |
800 return false; | 805 return false; |
801 } | 806 } |
802 } | 807 } |
803 | 808 |
804 if (options.avoidKeywordsInIdentifiers) { | 809 // TODO(floitsch): normally we should also check that the field is not a |
805 return !isJsKeyword(field.substring(1, field.length - 1)); | 810 // reserved word. We don't generate fields with reserved word names except |
806 } else { | 811 // for 'super'. |
807 // TODO(floitsch): normally we should also check that the field is not a | 812 return options.allowKeywordsInProperties || field != '"super"'; |
808 // reserved word. We don't generate fields with reserved word names excep
t | |
809 // for 'super'. | |
810 return field != '"super"'; | |
811 } | |
812 } | 813 } |
813 | 814 |
814 visitAccess(PropertyAccess access) { | 815 visitAccess(PropertyAccess access) { |
815 visitNestedExpression(access.receiver, CALL, | 816 visitNestedExpression(access.receiver, CALL, |
816 newInForInit: inForInit, | 817 newInForInit: inForInit, |
817 newAtStatementBegin: atStatementBegin); | 818 newAtStatementBegin: atStatementBegin); |
818 propertyNameOut(access.selector, inAccess: true); | 819 propertyNameOut(access.selector, inAccess: true); |
819 } | 820 } |
820 | 821 |
821 visitNamedFunction(NamedFunction namedFunction) { | 822 visitNamedFunction(NamedFunction namedFunction) { |
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1234 bool visitLabeledStatement(LabeledStatement node) | 1235 bool visitLabeledStatement(LabeledStatement node) |
1235 => node.body.accept(this); | 1236 => node.body.accept(this); |
1236 bool visitLiteralStatement(LiteralStatement node) => true; | 1237 bool visitLiteralStatement(LiteralStatement node) => true; |
1237 bool visitClassDeclaration(ClassDeclaration) => false; | 1238 bool visitClassDeclaration(ClassDeclaration) => false; |
1238 | 1239 |
1239 bool visitExpression(Expression node) => false; | 1240 bool visitExpression(Expression node) => false; |
1240 } | 1241 } |
1241 | 1242 |
1242 | 1243 |
1243 abstract class LocalNamer { | 1244 abstract class LocalNamer { |
1244 String getName(String oldName); | 1245 String getName(Identifier node); |
1245 void enterScope(Node node); | 1246 void enterScope(Node node); |
1246 void leaveScope(); | 1247 void leaveScope(); |
1247 } | 1248 } |
1248 | 1249 |
1249 | 1250 |
1250 class IdentityNamer implements LocalNamer { | 1251 class IdentityNamer implements LocalNamer { |
1251 String getName(String oldName) => oldName; | 1252 String getName(Identifier node) => node.name; |
1252 void enterScope(Node node) {} | 1253 void enterScope(Node node) {} |
1253 void leaveScope() {} | 1254 void leaveScope() {} |
1254 } | 1255 } |
1255 | 1256 |
1256 | 1257 |
1257 class MinifyRenamer implements LocalNamer { | 1258 class MinifyRenamer implements LocalNamer { |
1258 final List<Map<String, String>> maps = []; | 1259 final List<Map<String, String>> maps = []; |
1259 final List<int> parameterNumberStack = []; | 1260 final List<int> parameterNumberStack = []; |
1260 final List<int> variableNumberStack = []; | 1261 final List<int> variableNumberStack = []; |
1261 int parameterNumber = 0; | 1262 int parameterNumber = 0; |
1262 int variableNumber = 0; | 1263 int variableNumber = 0; |
1263 | 1264 |
1264 void enterScope(Node node) { | 1265 void enterScope(Node node) { |
1265 var vars = new VarCollector(); | 1266 var vars = new VarCollector(); |
1266 node.accept(vars); | 1267 node.accept(vars); |
1267 maps.add(new Map<String, String>()); | 1268 maps.add(new Map<String, String>()); |
1268 variableNumberStack.add(variableNumber); | 1269 variableNumberStack.add(variableNumber); |
1269 parameterNumberStack.add(parameterNumber); | 1270 parameterNumberStack.add(parameterNumber); |
1270 vars.forEachVar(declareVariable); | 1271 vars.forEachVar(declareVariable); |
1271 vars.forEachParam(declareParameter); | 1272 vars.forEachParam(declareParameter); |
1272 } | 1273 } |
1273 | 1274 |
1274 void leaveScope() { | 1275 void leaveScope() { |
1275 maps.removeLast(); | 1276 maps.removeLast(); |
1276 variableNumber = variableNumberStack.removeLast(); | 1277 variableNumber = variableNumberStack.removeLast(); |
1277 parameterNumber = parameterNumberStack.removeLast(); | 1278 parameterNumber = parameterNumberStack.removeLast(); |
1278 } | 1279 } |
1279 | 1280 |
1280 String getName(String oldName) { | 1281 String getName(Identifier node) { |
| 1282 String oldName = node.name; |
1281 // Go from inner scope to outer looking for mapping of name. | 1283 // Go from inner scope to outer looking for mapping of name. |
1282 for (int i = maps.length - 1; i >= 0; i--) { | 1284 for (int i = maps.length - 1; i >= 0; i--) { |
1283 var map = maps[i]; | 1285 var map = maps[i]; |
1284 var replacement = map[oldName]; | 1286 var replacement = map[oldName]; |
1285 if (replacement != null) return replacement; | 1287 if (replacement != null) return replacement; |
1286 } | 1288 } |
1287 return oldName; | 1289 return oldName; |
1288 } | 1290 } |
1289 | 1291 |
1290 static const LOWER_CASE_LETTERS = 26; | 1292 static const LOWER_CASE_LETTERS = 26; |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1367 codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); | 1369 codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); |
1368 } | 1370 } |
1369 codes.add(charCodes.$0 + digit); | 1371 codes.add(charCodes.$0 + digit); |
1370 newName = new String.fromCharCodes(codes); | 1372 newName = new String.fromCharCodes(codes); |
1371 } | 1373 } |
1372 assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); | 1374 assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); |
1373 maps.last[oldName] = newName; | 1375 maps.last[oldName] = newName; |
1374 return newName; | 1376 return newName; |
1375 } | 1377 } |
1376 } | 1378 } |
OLD | NEW |