golf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +21 -0
  4. data/Rakefile +12 -0
  5. data/bin/golf +8 -0
  6. data/golf-java/README.markdown +41 -0
  7. data/golf-java/THANKS.markdown +14 -0
  8. data/golf-java/build/classes.zip +0 -0
  9. data/golf-java/build/com/thinkminimo/golf/GolfResource$MimeMapping$MimeMappingSingleton.class +0 -0
  10. data/golf-java/build/com/thinkminimo/golf/GolfResource$MimeMapping.class +0 -0
  11. data/golf-java/build/com/thinkminimo/golf/GolfResource.class +0 -0
  12. data/golf-java/build/com/thinkminimo/golf/GolfServlet$1.class +0 -0
  13. data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfContext.class +0 -0
  14. data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfParams.class +0 -0
  15. data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfSession.class +0 -0
  16. data/golf-java/build/com/thinkminimo/golf/GolfServlet$PermanentRedirectException.class +0 -0
  17. data/golf-java/build/com/thinkminimo/golf/GolfServlet$RedirectException.class +0 -0
  18. data/golf-java/build/com/thinkminimo/golf/GolfServlet$StoredJSVM.class +0 -0
  19. data/golf-java/build/com/thinkminimo/golf/GolfServlet.class +0 -0
  20. data/golf-java/build/com/thinkminimo/golf/JsonpTunnel.class +0 -0
  21. data/golf-java/build/com/thinkminimo/golf/Main$1.class +0 -0
  22. data/golf-java/build/com/thinkminimo/golf/Main$RingList.class +0 -0
  23. data/golf-java/build/com/thinkminimo/golf/Main.class +0 -0
  24. data/golf-java/build/com/thinkminimo/golf/ProxyServlet.class +0 -0
  25. data/golf-java/build/com/yahoo/platform/yui/compressor/CssCompressor.class +0 -0
  26. data/golf-java/build/com/yahoo/platform/yui/compressor/JarClassLoader.class +0 -0
  27. data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptCompressor.class +0 -0
  28. data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.class +0 -0
  29. data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptToken.class +0 -0
  30. data/golf-java/build/com/yahoo/platform/yui/compressor/ScriptOrFnScope.class +0 -0
  31. data/golf-java/build/depends.zip +0 -0
  32. data/golf-java/build/golf +2 -0
  33. data/golf-java/build/org/json/JSONArray.class +0 -0
  34. data/golf-java/build/org/json/JSONException.class +0 -0
  35. data/golf-java/build/org/json/JSONObject$1.class +0 -0
  36. data/golf-java/build/org/json/JSONObject$Null.class +0 -0
  37. data/golf-java/build/org/json/JSONObject.class +0 -0
  38. data/golf-java/build/org/json/JSONString.class +0 -0
  39. data/golf-java/build/org/json/JSONStringer.class +0 -0
  40. data/golf-java/build/org/json/JSONTokener.class +0 -0
  41. data/golf-java/build/org/json/JSONWriter.class +0 -0
  42. data/golf-java/build/resources.zip +0 -0
  43. data/golf-java/build.xml +174 -0
  44. data/golf-java/dist/golf.zip +0 -0
  45. data/golf-java/lib/ant-launcher.jar +0 -0
  46. data/golf-java/lib/ant.jar +0 -0
  47. data/golf-java/lib/commons-codec-1.4.jar +0 -0
  48. data/golf-java/lib/commons-collections-3.2.1.jar +0 -0
  49. data/golf-java/lib/commons-fileupload-1.2.1.jar +0 -0
  50. data/golf-java/lib/commons-httpclient-3.1.jar +0 -0
  51. data/golf-java/lib/commons-io-1.4.jar +0 -0
  52. data/golf-java/lib/commons-lang-2.4.jar +0 -0
  53. data/golf-java/lib/commons-logging-1.1.1.jar +0 -0
  54. data/golf-java/lib/cssparser-0.9.5.jar +0 -0
  55. data/golf-java/lib/getopt-0.1-dev.jar +0 -0
  56. data/golf-java/lib/htmlunit-2.6.jar +0 -0
  57. data/golf-java/lib/htmlunit-core-js-2.6.jar +0 -0
  58. data/golf-java/lib/java-xmlbuilder-1.jar +0 -0
  59. data/golf-java/lib/jets3t-0.7.0.jar +0 -0
  60. data/golf-java/lib/jetty-6.1.15.jar +0 -0
  61. data/golf-java/lib/jetty-util-6.1.15.jar +0 -0
  62. data/golf-java/lib/log4j-1.2.15.jar +0 -0
  63. data/golf-java/lib/nekohtml-1.9.13.jar +0 -0
  64. data/golf-java/lib/sac-1.3.jar +0 -0
  65. data/golf-java/lib/serializer-2.7.1.jar +0 -0
  66. data/golf-java/lib/servlet-api-2.5-20081211.jar +0 -0
  67. data/golf-java/lib/xalan-2.7.1.jar +0 -0
  68. data/golf-java/lib/xercesImpl-2.9.1.jar +0 -0
  69. data/golf-java/lib/xml-apis-1.3.04.jar +0 -0
  70. data/golf-java/resources/app_error.html +60 -0
  71. data/golf-java/resources/error.html +29 -0
  72. data/golf-java/resources/forcebot.txt +0 -0
  73. data/golf-java/resources/forceclient.txt +0 -0
  74. data/golf-java/resources/forceproxy.txt +0 -0
  75. data/golf-java/resources/head.html +0 -0
  76. data/golf-java/resources/jquery.address.js +439 -0
  77. data/golf-java/resources/jquery.golf.js +945 -0
  78. data/golf-java/resources/jquery.js +4376 -0
  79. data/golf-java/resources/jsdetect.html +23 -0
  80. data/golf-java/resources/loading.gif +0 -0
  81. data/golf-java/resources/new.html +45 -0
  82. data/golf-java/resources/new.static.html +45 -0
  83. data/golf-java/resources/noscript.forceclient.html +11 -0
  84. data/golf-java/resources/noscript.html +18 -0
  85. data/golf-java/resources/noscript.static.html +15 -0
  86. data/golf-java/resources/project.xml +21 -0
  87. data/golf-java/resources/proxy_project.xml +11 -0
  88. data/golf-java/resources/proxy_web.xml +40 -0
  89. data/golf-java/resources/static_project.xml +37 -0
  90. data/golf-java/resources/version +1 -0
  91. data/golf-java/resources/web.xml +36 -0
  92. data/golf-java/resources/welcome2golf.html +50 -0
  93. data/golf-java/src/com/thinkminimo/golf/GolfResource.java +582 -0
  94. data/golf-java/src/com/thinkminimo/golf/GolfServlet.java +1055 -0
  95. data/golf-java/src/com/thinkminimo/golf/JsonpTunnel.java +86 -0
  96. data/golf-java/src/com/thinkminimo/golf/Main.java +1299 -0
  97. data/golf-java/src/com/thinkminimo/golf/ProxyServlet.java +577 -0
  98. data/golf-java/src/com/yahoo/platform/yui/compressor/CssCompressor.java +188 -0
  99. data/golf-java/src/com/yahoo/platform/yui/compressor/JarClassLoader.java +158 -0
  100. data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptCompressor.java +1281 -0
  101. data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.java +55 -0
  102. data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptToken.java +28 -0
  103. data/golf-java/src/com/yahoo/platform/yui/compressor/ScriptOrFnScope.java +160 -0
  104. data/golf-java/src/org/json/JSONArray.java +934 -0
  105. data/golf-java/src/org/json/JSONException.java +27 -0
  106. data/golf-java/src/org/json/JSONObject.java +1550 -0
  107. data/golf-java/src/org/json/JSONString.java +18 -0
  108. data/golf-java/src/org/json/JSONStringer.java +78 -0
  109. data/golf-java/src/org/json/JSONTokener.java +422 -0
  110. data/golf-java/src/org/json/JSONWriter.java +323 -0
  111. data/golf-java/test/client/golftest/README.markdown +3 -0
  112. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.css +114 -0
  113. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.html +17 -0
  114. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.js +81 -0
  115. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/asc.gif +0 -0
  116. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/bg.gif +0 -0
  117. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/desc.gif +0 -0
  118. data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/test/test.html +1 -0
  119. data/golf-java/test/client/golftest/controller.js +131 -0
  120. data/golf-java/test/client/golftest/forceclient.txt +0 -0
  121. data/golf-java/test/client/golftest/forceproxy.txt +0 -0
  122. data/golf-java/test/client/golftest/head.html +1 -0
  123. data/golf-java/test/client/golftest/scripts/jquery.tablesort.js +75 -0
  124. data/golf-java/test/client/golftest/styles/style.css +18 -0
  125. data/golf-java/test/client/golftest/test/test.html +1 -0
  126. data/golf.gemspec +24 -0
  127. data/lib/golf/cli.rb +32 -0
  128. data/lib/golf/compiler.rb +12 -0
  129. data/lib/golf/rack.rb +17 -0
  130. data/lib/golf/version.rb +3 -0
  131. data/lib/golf.rb +6 -0
  132. data/template/Gemfile +5 -0
  133. data/template/config.ru +7 -0
  134. data/template/golfapp/components/Test/Test.html +22 -0
  135. data/template/golfapp/components/Test/Test.res/myfile.txt +1 -0
  136. data/template/golfapp/controller.js +10 -0
  137. data/template/golfapp/plugins/mylib.js +12 -0
  138. data/test/test_compiler.rb +10 -0
  139. data/test/test_helper.rb +6 -0
  140. data/test/test_rack.rb +17 -0
  141. metadata +208 -0
@@ -0,0 +1,1281 @@
1
+ /*
2
+ * YUI Compressor
3
+ * Author: Julien Lecomte <jlecomte@yahoo-inc.com>
4
+ * Copyright (c) 2007, Yahoo! Inc. All rights reserved.
5
+ * Code licensed under the BSD License:
6
+ * http://developer.yahoo.net/yui/license.txt
7
+ */
8
+
9
+ package com.yahoo.platform.yui.compressor;
10
+
11
+ import net.sourceforge.htmlunit.corejs.javascript.*;
12
+
13
+ import java.io.IOException;
14
+ import java.io.Reader;
15
+ import java.io.Writer;
16
+ import java.util.*;
17
+ import java.util.regex.Matcher;
18
+ import java.util.regex.Pattern;
19
+
20
+ public class JavaScriptCompressor {
21
+
22
+ static final ArrayList ones;
23
+ static final ArrayList twos;
24
+ static final ArrayList threes;
25
+
26
+ static final Set builtin = new HashSet();
27
+ static final Map literals = new Hashtable();
28
+ static final Set reserved = new HashSet();
29
+
30
+ static {
31
+
32
+ // This list contains all the 3 characters or less built-in global
33
+ // symbols available in a browser. Please add to this list if you
34
+ // see anything missing.
35
+ builtin.add("NaN");
36
+ builtin.add("top");
37
+
38
+ ones = new ArrayList();
39
+ for (char c = 'a'; c <= 'z'; c++)
40
+ ones.add(Character.toString(c));
41
+ for (char c = 'A'; c <= 'Z'; c++)
42
+ ones.add(Character.toString(c));
43
+
44
+ twos = new ArrayList();
45
+ for (int i = 0; i < ones.size(); i++) {
46
+ String one = (String) ones.get(i);
47
+ for (char c = 'a'; c <= 'z'; c++)
48
+ twos.add(one + Character.toString(c));
49
+ for (char c = 'A'; c <= 'Z'; c++)
50
+ twos.add(one + Character.toString(c));
51
+ for (char c = '0'; c <= '9'; c++)
52
+ twos.add(one + Character.toString(c));
53
+ }
54
+
55
+ // Remove two-letter JavaScript reserved words and built-in globals...
56
+ twos.remove("as");
57
+ twos.remove("is");
58
+ twos.remove("do");
59
+ twos.remove("if");
60
+ twos.remove("in");
61
+ twos.removeAll(builtin);
62
+
63
+ threes = new ArrayList();
64
+ for (int i = 0; i < twos.size(); i++) {
65
+ String two = (String) twos.get(i);
66
+ for (char c = 'a'; c <= 'z'; c++)
67
+ threes.add(two + Character.toString(c));
68
+ for (char c = 'A'; c <= 'Z'; c++)
69
+ threes.add(two + Character.toString(c));
70
+ for (char c = '0'; c <= '9'; c++)
71
+ threes.add(two + Character.toString(c));
72
+ }
73
+
74
+ // Remove three-letter JavaScript reserved words and built-in globals...
75
+ threes.remove("for");
76
+ threes.remove("int");
77
+ threes.remove("new");
78
+ threes.remove("try");
79
+ threes.remove("use");
80
+ threes.remove("var");
81
+ threes.removeAll(builtin);
82
+
83
+ // That's up to ((26+26)*(1+(26+26+10)))*(1+(26+26+10))-8
84
+ // (206,380 symbols per scope)
85
+
86
+ // The following list comes from org/mozilla/javascript/Decompiler.java...
87
+ literals.put(new Integer(Token.GET), "get ");
88
+ literals.put(new Integer(Token.SET), "set ");
89
+ literals.put(new Integer(Token.TRUE), "true");
90
+ literals.put(new Integer(Token.FALSE), "false");
91
+ literals.put(new Integer(Token.NULL), "null");
92
+ literals.put(new Integer(Token.THIS), "this");
93
+ literals.put(new Integer(Token.FUNCTION), "function");
94
+ literals.put(new Integer(Token.COMMA), ",");
95
+ literals.put(new Integer(Token.LC), "{");
96
+ literals.put(new Integer(Token.RC), "}");
97
+ literals.put(new Integer(Token.LP), "(");
98
+ literals.put(new Integer(Token.RP), ")");
99
+ literals.put(new Integer(Token.LB), "[");
100
+ literals.put(new Integer(Token.RB), "]");
101
+ literals.put(new Integer(Token.DOT), ".");
102
+ literals.put(new Integer(Token.NEW), "new ");
103
+ literals.put(new Integer(Token.DELPROP), "delete ");
104
+ literals.put(new Integer(Token.IF), "if");
105
+ literals.put(new Integer(Token.ELSE), "else");
106
+ literals.put(new Integer(Token.FOR), "for");
107
+ literals.put(new Integer(Token.IN), " in ");
108
+ literals.put(new Integer(Token.WITH), "with");
109
+ literals.put(new Integer(Token.WHILE), "while");
110
+ literals.put(new Integer(Token.DO), "do");
111
+ literals.put(new Integer(Token.TRY), "try");
112
+ literals.put(new Integer(Token.CATCH), "catch");
113
+ literals.put(new Integer(Token.FINALLY), "finally");
114
+ literals.put(new Integer(Token.THROW), "throw");
115
+ literals.put(new Integer(Token.SWITCH), "switch");
116
+ literals.put(new Integer(Token.BREAK), "break");
117
+ literals.put(new Integer(Token.CONTINUE), "continue");
118
+ literals.put(new Integer(Token.CASE), "case");
119
+ literals.put(new Integer(Token.DEFAULT), "default");
120
+ literals.put(new Integer(Token.RETURN), "return");
121
+ literals.put(new Integer(Token.VAR), "var ");
122
+ literals.put(new Integer(Token.SEMI), ";");
123
+ literals.put(new Integer(Token.ASSIGN), "=");
124
+ literals.put(new Integer(Token.ASSIGN_ADD), "+=");
125
+ literals.put(new Integer(Token.ASSIGN_SUB), "-=");
126
+ literals.put(new Integer(Token.ASSIGN_MUL), "*=");
127
+ literals.put(new Integer(Token.ASSIGN_DIV), "/=");
128
+ literals.put(new Integer(Token.ASSIGN_MOD), "%=");
129
+ literals.put(new Integer(Token.ASSIGN_BITOR), "|=");
130
+ literals.put(new Integer(Token.ASSIGN_BITXOR), "^=");
131
+ literals.put(new Integer(Token.ASSIGN_BITAND), "&=");
132
+ literals.put(new Integer(Token.ASSIGN_LSH), "<<=");
133
+ literals.put(new Integer(Token.ASSIGN_RSH), ">>=");
134
+ literals.put(new Integer(Token.ASSIGN_URSH), ">>>=");
135
+ literals.put(new Integer(Token.HOOK), "?");
136
+ literals.put(new Integer(Token.OBJECTLIT), ":");
137
+ literals.put(new Integer(Token.COLON), ":");
138
+ literals.put(new Integer(Token.OR), "||");
139
+ literals.put(new Integer(Token.AND), "&&");
140
+ literals.put(new Integer(Token.BITOR), "|");
141
+ literals.put(new Integer(Token.BITXOR), "^");
142
+ literals.put(new Integer(Token.BITAND), "&");
143
+ literals.put(new Integer(Token.SHEQ), "===");
144
+ literals.put(new Integer(Token.SHNE), "!==");
145
+ literals.put(new Integer(Token.EQ), "==");
146
+ literals.put(new Integer(Token.NE), "!=");
147
+ literals.put(new Integer(Token.LE), "<=");
148
+ literals.put(new Integer(Token.LT), "<");
149
+ literals.put(new Integer(Token.GE), ">=");
150
+ literals.put(new Integer(Token.GT), ">");
151
+ literals.put(new Integer(Token.INSTANCEOF), " instanceof ");
152
+ literals.put(new Integer(Token.LSH), "<<");
153
+ literals.put(new Integer(Token.RSH), ">>");
154
+ literals.put(new Integer(Token.URSH), ">>>");
155
+ literals.put(new Integer(Token.TYPEOF), "typeof");
156
+ literals.put(new Integer(Token.VOID), "void ");
157
+ literals.put(new Integer(Token.CONST), "const ");
158
+ literals.put(new Integer(Token.NOT), "!");
159
+ literals.put(new Integer(Token.BITNOT), "~");
160
+ literals.put(new Integer(Token.POS), "+");
161
+ literals.put(new Integer(Token.NEG), "-");
162
+ literals.put(new Integer(Token.INC), "++");
163
+ literals.put(new Integer(Token.DEC), "--");
164
+ literals.put(new Integer(Token.ADD), "+");
165
+ literals.put(new Integer(Token.SUB), "-");
166
+ literals.put(new Integer(Token.MUL), "*");
167
+ literals.put(new Integer(Token.DIV), "/");
168
+ literals.put(new Integer(Token.MOD), "%");
169
+ literals.put(new Integer(Token.COLONCOLON), "::");
170
+ literals.put(new Integer(Token.DOTDOT), "..");
171
+ literals.put(new Integer(Token.DOTQUERY), ".(");
172
+ literals.put(new Integer(Token.XMLATTR), "@");
173
+
174
+ // See http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Reserved_Words
175
+
176
+ // JavaScript 1.5 reserved words
177
+ reserved.add("break");
178
+ reserved.add("case");
179
+ reserved.add("catch");
180
+ reserved.add("continue");
181
+ reserved.add("default");
182
+ reserved.add("delete");
183
+ reserved.add("do");
184
+ reserved.add("else");
185
+ reserved.add("finally");
186
+ reserved.add("for");
187
+ reserved.add("function");
188
+ reserved.add("if");
189
+ reserved.add("in");
190
+ reserved.add("instanceof");
191
+ reserved.add("new");
192
+ reserved.add("return");
193
+ reserved.add("switch");
194
+ reserved.add("this");
195
+ reserved.add("throw");
196
+ reserved.add("try");
197
+ reserved.add("typeof");
198
+ reserved.add("var");
199
+ reserved.add("void");
200
+ reserved.add("while");
201
+ reserved.add("with");
202
+ // Words reserved for future use
203
+ reserved.add("abstract");
204
+ reserved.add("boolean");
205
+ reserved.add("byte");
206
+ reserved.add("char");
207
+ reserved.add("class");
208
+ reserved.add("const");
209
+ reserved.add("debugger");
210
+ reserved.add("double");
211
+ reserved.add("enum");
212
+ reserved.add("export");
213
+ reserved.add("extends");
214
+ reserved.add("final");
215
+ reserved.add("float");
216
+ reserved.add("goto");
217
+ reserved.add("implements");
218
+ reserved.add("import");
219
+ reserved.add("int");
220
+ reserved.add("interface");
221
+ reserved.add("long");
222
+ reserved.add("native");
223
+ reserved.add("package");
224
+ reserved.add("private");
225
+ reserved.add("protected");
226
+ reserved.add("public");
227
+ reserved.add("short");
228
+ reserved.add("static");
229
+ reserved.add("super");
230
+ reserved.add("synchronized");
231
+ reserved.add("throws");
232
+ reserved.add("transient");
233
+ reserved.add("volatile");
234
+ // These are not reserved, but should be taken into account
235
+ // in isValidIdentifier (See jslint source code)
236
+ reserved.add("arguments");
237
+ reserved.add("eval");
238
+ reserved.add("true");
239
+ reserved.add("false");
240
+ reserved.add("Infinity");
241
+ reserved.add("NaN");
242
+ reserved.add("null");
243
+ reserved.add("undefined");
244
+ }
245
+
246
+ private static int countChar(String haystack, char needle) {
247
+ int idx = 0;
248
+ int count = 0;
249
+ int length = haystack.length();
250
+ while (idx < length) {
251
+ char c = haystack.charAt(idx++);
252
+ if (c == needle) {
253
+ count++;
254
+ }
255
+ }
256
+ return count;
257
+ }
258
+
259
+ private static int printSourceString(String source, int offset, StringBuffer sb) {
260
+ int length = source.charAt(offset);
261
+ ++offset;
262
+ if ((0x8000 & length) != 0) {
263
+ length = ((0x7FFF & length) << 16) | source.charAt(offset);
264
+ ++offset;
265
+ }
266
+ if (sb != null) {
267
+ String str = source.substring(offset, offset + length);
268
+ sb.append(str);
269
+ }
270
+ return offset + length;
271
+ }
272
+
273
+ private static int printSourceNumber(String source,
274
+ int offset, StringBuffer sb) {
275
+ double number = 0.0;
276
+ char type = source.charAt(offset);
277
+ ++offset;
278
+ if (type == 'S') {
279
+ if (sb != null) {
280
+ number = source.charAt(offset);
281
+ }
282
+ ++offset;
283
+ } else if (type == 'J' || type == 'D') {
284
+ if (sb != null) {
285
+ long lbits;
286
+ lbits = (long) source.charAt(offset) << 48;
287
+ lbits |= (long) source.charAt(offset + 1) << 32;
288
+ lbits |= (long) source.charAt(offset + 2) << 16;
289
+ lbits |= (long) source.charAt(offset + 3);
290
+ if (type == 'J') {
291
+ number = lbits;
292
+ } else {
293
+ number = Double.longBitsToDouble(lbits);
294
+ }
295
+ }
296
+ offset += 4;
297
+ } else {
298
+ // Bad source
299
+ throw new RuntimeException();
300
+ }
301
+ if (sb != null) {
302
+ sb.append(ScriptRuntime.numberToString(number, 10));
303
+ }
304
+ return offset;
305
+ }
306
+
307
+ private static ArrayList parse(Reader in, ErrorReporter reporter)
308
+ throws IOException, EvaluatorException {
309
+
310
+ CompilerEnvirons env = new CompilerEnvirons();
311
+ Parser parser = new Parser(env, reporter);
312
+ parser.parse(in, null, 1);
313
+ String source = ""; //FIXME parser.getEncodedSource();
314
+
315
+ int offset = 0;
316
+ int length = source.length();
317
+ ArrayList tokens = new ArrayList();
318
+ StringBuffer sb = new StringBuffer();
319
+
320
+ while (offset < length) {
321
+ int tt = source.charAt(offset++);
322
+ switch (tt) {
323
+
324
+ case Token.NAME:
325
+ case Token.REGEXP:
326
+ case Token.STRING:
327
+ sb.setLength(0);
328
+ offset = printSourceString(source, offset, sb);
329
+ tokens.add(new JavaScriptToken(tt, sb.toString()));
330
+ break;
331
+
332
+ case Token.NUMBER:
333
+ sb.setLength(0);
334
+ offset = printSourceNumber(source, offset, sb);
335
+ tokens.add(new JavaScriptToken(tt, sb.toString()));
336
+ break;
337
+
338
+ default:
339
+ String literal = (String) literals.get(new Integer(tt));
340
+ if (literal != null) {
341
+ tokens.add(new JavaScriptToken(tt, literal));
342
+ }
343
+ break;
344
+ }
345
+ }
346
+
347
+ return tokens;
348
+ }
349
+
350
+ private static void processStringLiterals(ArrayList tokens, boolean merge) {
351
+
352
+ String tv;
353
+ int i, length = tokens.size();
354
+ JavaScriptToken token, prevToken, nextToken;
355
+
356
+ if (merge) {
357
+
358
+ // Concatenate string literals that are being appended wherever
359
+ // it is safe to do so. Note that we take care of the case:
360
+ // "a" + "b".toUpperCase()
361
+
362
+ for (i = 0; i < length; i++) {
363
+ token = (JavaScriptToken) tokens.get(i);
364
+ switch (token.getType()) {
365
+
366
+ case Token.ADD:
367
+ if (i > 0 && i < length) {
368
+ prevToken = (JavaScriptToken) tokens.get(i - 1);
369
+ nextToken = (JavaScriptToken) tokens.get(i + 1);
370
+ if (prevToken.getType() == Token.STRING && nextToken.getType() == Token.STRING &&
371
+ (i == length - 1 || ((JavaScriptToken) tokens.get(i + 2)).getType() != Token.DOT)) {
372
+ tokens.set(i - 1, new JavaScriptToken(Token.STRING,
373
+ prevToken.getValue() + nextToken.getValue()));
374
+ tokens.remove(i + 1);
375
+ tokens.remove(i);
376
+ i = i - 1;
377
+ length = length - 2;
378
+ break;
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ }
385
+
386
+ // Second pass...
387
+
388
+ for (i = 0; i < length; i++) {
389
+ token = (JavaScriptToken) tokens.get(i);
390
+ if (token.getType() == Token.STRING) {
391
+ tv = token.getValue();
392
+
393
+ // Finally, add the quoting characters and escape the string. We use
394
+ // the quoting character that minimizes the amount of escaping to save
395
+ // a few additional bytes.
396
+
397
+ char quotechar;
398
+ int singleQuoteCount = countChar(tv, '\'');
399
+ int doubleQuoteCount = countChar(tv, '"');
400
+ if (doubleQuoteCount <= singleQuoteCount) {
401
+ quotechar = '"';
402
+ } else {
403
+ quotechar = '\'';
404
+ }
405
+
406
+ tv = quotechar + escapeString(tv, quotechar) + quotechar;
407
+
408
+ // String concatenation transforms the old script scheme:
409
+ // '<scr'+'ipt ...><'+'/script>'
410
+ // into the following:
411
+ // '<script ...></script>'
412
+ // which breaks if this code is embedded inside an HTML document.
413
+ // Since this is not the right way to do this, let's fix the code by
414
+ // transforming all "</script" into "<\/script"
415
+
416
+ if (tv.indexOf("</script") >= 0) {
417
+ tv = tv.replaceAll("<\\/script", "<\\\\/script");
418
+ }
419
+
420
+ tokens.set(i, new JavaScriptToken(Token.STRING, tv));
421
+ }
422
+ }
423
+ }
424
+
425
+ // Add necessary escaping that was removed in Rhino's tokenizer.
426
+ private static String escapeString(String s, char quotechar) {
427
+
428
+ assert quotechar == '"' || quotechar == '\'';
429
+
430
+ if (s == null) {
431
+ return null;
432
+ }
433
+
434
+ StringBuffer sb = new StringBuffer();
435
+ for (int i = 0, L = s.length(); i < L; i++) {
436
+ int c = s.charAt(i);
437
+ if (c == quotechar) {
438
+ sb.append("\\");
439
+ }
440
+ sb.append((char) c);
441
+ }
442
+
443
+ return sb.toString();
444
+ }
445
+
446
+ /*
447
+ * Simple check to see whether a string is a valid identifier name.
448
+ * If a string matches this pattern, it means it IS a valid
449
+ * identifier name. If a string doesn't match it, it does not
450
+ * necessarily mean it is not a valid identifier name.
451
+ */
452
+ private static final Pattern SIMPLE_IDENTIFIER_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
453
+
454
+ private static boolean isValidIdentifier(String s) {
455
+ Matcher m = SIMPLE_IDENTIFIER_NAME_PATTERN.matcher(s);
456
+ return (m.matches() && !reserved.contains(s));
457
+ }
458
+
459
+ /*
460
+ * Transforms obj["foo"] into obj.foo whenever possible, saving 3 bytes.
461
+ */
462
+ private static void optimizeObjectMemberAccess(ArrayList tokens) {
463
+
464
+ String tv;
465
+ int i, length;
466
+ JavaScriptToken token;
467
+
468
+ for (i = 0, length = tokens.size(); i < length; i++) {
469
+
470
+ if (((JavaScriptToken) tokens.get(i)).getType() == Token.LB &&
471
+ i > 0 && i < length - 2 &&
472
+ ((JavaScriptToken) tokens.get(i - 1)).getType() == Token.NAME &&
473
+ ((JavaScriptToken) tokens.get(i + 1)).getType() == Token.STRING &&
474
+ ((JavaScriptToken) tokens.get(i + 2)).getType() == Token.RB) {
475
+ token = (JavaScriptToken) tokens.get(i + 1);
476
+ tv = token.getValue();
477
+ tv = tv.substring(1, tv.length() - 1);
478
+ if (isValidIdentifier(tv)) {
479
+ tokens.set(i, new JavaScriptToken(Token.DOT, "."));
480
+ tokens.set(i + 1, new JavaScriptToken(Token.NAME, tv));
481
+ tokens.remove(i + 2);
482
+ i = i + 2;
483
+ length = length - 1;
484
+ }
485
+ }
486
+ }
487
+ }
488
+
489
+ /*
490
+ * Transforms 'foo': ... into foo: ... whenever possible, saving 2 bytes.
491
+ */
492
+ private static void optimizeObjLitMemberDecl(ArrayList tokens) {
493
+
494
+ String tv;
495
+ int i, length;
496
+ JavaScriptToken token;
497
+
498
+ for (i = 0, length = tokens.size(); i < length; i++) {
499
+ if (((JavaScriptToken) tokens.get(i)).getType() == Token.OBJECTLIT &&
500
+ i > 0 && ((JavaScriptToken) tokens.get(i - 1)).getType() == Token.STRING) {
501
+ token = (JavaScriptToken) tokens.get(i - 1);
502
+ tv = token.getValue();
503
+ tv = tv.substring(1, tv.length() - 1);
504
+ if (isValidIdentifier(tv)) {
505
+ tokens.set(i - 1, new JavaScriptToken(Token.NAME, tv));
506
+ }
507
+ }
508
+ }
509
+ }
510
+
511
+ private ErrorReporter logger;
512
+
513
+ private boolean munge;
514
+ private boolean verbose;
515
+
516
+ private static final int BUILDING_SYMBOL_TREE = 1;
517
+ private static final int CHECKING_SYMBOL_TREE = 2;
518
+
519
+ private int mode;
520
+ private int offset;
521
+ private int braceNesting;
522
+ private ArrayList tokens;
523
+ private Stack scopes = new Stack();
524
+ private ScriptOrFnScope globalScope = new ScriptOrFnScope(-1, null);
525
+ private Hashtable indexedScopes = new Hashtable();
526
+
527
+ public JavaScriptCompressor(Reader in, ErrorReporter reporter)
528
+ throws IOException, EvaluatorException {
529
+
530
+ this.logger = reporter;
531
+ this.tokens = parse(in, reporter);
532
+ }
533
+
534
+ public void compress(Writer out, int linebreak, boolean munge, boolean verbose,
535
+ boolean preserveAllSemiColons, boolean disableOptimizations)
536
+ throws IOException {
537
+
538
+ this.munge = munge;
539
+ this.verbose = verbose;
540
+
541
+ processStringLiterals(this.tokens, !disableOptimizations);
542
+
543
+ if (!disableOptimizations) {
544
+ optimizeObjectMemberAccess(this.tokens);
545
+ optimizeObjLitMemberDecl(this.tokens);
546
+ }
547
+
548
+ buildSymbolTree();
549
+ // DO NOT TOUCH this.tokens BETWEEN THESE TWO PHASES (BECAUSE OF this.indexedScopes)
550
+ mungeSymboltree();
551
+ StringBuffer sb = printSymbolTree(linebreak, preserveAllSemiColons);
552
+
553
+ out.write(sb.toString());
554
+ }
555
+
556
+ private ScriptOrFnScope getCurrentScope() {
557
+ return (ScriptOrFnScope) scopes.peek();
558
+ }
559
+
560
+ private void enterScope(ScriptOrFnScope scope) {
561
+ scopes.push(scope);
562
+ }
563
+
564
+ private void leaveCurrentScope() {
565
+ scopes.pop();
566
+ }
567
+
568
+ private JavaScriptToken consumeToken() {
569
+ return (JavaScriptToken) tokens.get(offset++);
570
+ }
571
+
572
+ private JavaScriptToken getToken(int delta) {
573
+ return (JavaScriptToken) tokens.get(offset + delta);
574
+ }
575
+
576
+ /*
577
+ * Returns the identifier for the specified symbol defined in
578
+ * the specified scope or in any scope above it. Returns null
579
+ * if this symbol does not have a corresponding identifier.
580
+ */
581
+ private JavaScriptIdentifier getIdentifier(String symbol, ScriptOrFnScope scope) {
582
+ JavaScriptIdentifier identifier;
583
+ while (scope != null) {
584
+ identifier = scope.getIdentifier(symbol);
585
+ if (identifier != null) {
586
+ return identifier;
587
+ }
588
+ scope = scope.getParentScope();
589
+ }
590
+ return null;
591
+ }
592
+
593
+ /*
594
+ * If either 'eval' or 'with' is used in a local scope, we must make
595
+ * sure that all containing local scopes don't get munged. Otherwise,
596
+ * the obfuscation would potentially introduce bugs.
597
+ */
598
+ private void protectScopeFromObfuscation(ScriptOrFnScope scope) {
599
+ assert scope != null;
600
+
601
+ if (scope == globalScope) {
602
+ // The global scope does not get obfuscated,
603
+ // so we don't need to worry about it...
604
+ return;
605
+ }
606
+
607
+ // Find the highest local scope containing the specified scope.
608
+ while (scope.getParentScope() != globalScope) {
609
+ scope = scope.getParentScope();
610
+ }
611
+
612
+ assert scope.getParentScope() == globalScope;
613
+ scope.preventMunging();
614
+ }
615
+
616
+ private String getDebugString(int max) {
617
+ assert max > 0;
618
+ StringBuffer result = new StringBuffer();
619
+ int start = Math.max(offset - max, 0);
620
+ int end = Math.min(offset + max, tokens.size());
621
+ for (int i = start; i < end; i++) {
622
+ JavaScriptToken token = (JavaScriptToken) tokens.get(i);
623
+ if (i == offset - 1) {
624
+ result.append(" ---> ");
625
+ }
626
+ result.append(token.getValue());
627
+ if (i == offset - 1) {
628
+ result.append(" <--- ");
629
+ }
630
+ }
631
+ return result.toString();
632
+ }
633
+
634
+ private void warn(String message, boolean showDebugString) {
635
+ if (verbose) {
636
+ if (showDebugString) {
637
+ message = message + "\n" + getDebugString(10);
638
+ }
639
+ logger.warning(message, null, -1, null, -1);
640
+ }
641
+ }
642
+
643
+ private void parseFunctionDeclaration() {
644
+
645
+ String symbol;
646
+ JavaScriptToken token;
647
+ ScriptOrFnScope currentScope, fnScope;
648
+ JavaScriptIdentifier identifier;
649
+
650
+ currentScope = getCurrentScope();
651
+
652
+ token = consumeToken();
653
+ if (token.getType() == Token.NAME) {
654
+ if (mode == BUILDING_SYMBOL_TREE) {
655
+ // Get the name of the function and declare it in the current scope.
656
+ symbol = token.getValue();
657
+ if (currentScope.getIdentifier(symbol) != null) {
658
+ warn("The function " + symbol + " has already been declared in the same scope...", true);
659
+ }
660
+ currentScope.declareIdentifier(symbol);
661
+ }
662
+ token = consumeToken();
663
+ }
664
+
665
+ assert token.getType() == Token.LP;
666
+ if (mode == BUILDING_SYMBOL_TREE) {
667
+ fnScope = new ScriptOrFnScope(braceNesting, currentScope);
668
+ indexedScopes.put(new Integer(offset), fnScope);
669
+ } else {
670
+ fnScope = (ScriptOrFnScope) indexedScopes.get(new Integer(offset));
671
+ }
672
+
673
+ // Parse function arguments.
674
+ int argpos = 0;
675
+ while ((token = consumeToken()).getType() != Token.RP) {
676
+ assert token.getType() == Token.NAME ||
677
+ token.getType() == Token.COMMA;
678
+ if (token.getType() == Token.NAME && mode == BUILDING_SYMBOL_TREE) {
679
+ symbol = token.getValue();
680
+ identifier = fnScope.declareIdentifier(symbol);
681
+ if (symbol.equals("$super") && argpos == 0) {
682
+ // Exception for Prototype 1.6...
683
+ identifier.preventMunging();
684
+ }
685
+ argpos++;
686
+ }
687
+ }
688
+
689
+ token = consumeToken();
690
+ assert token.getType() == Token.LC;
691
+ braceNesting++;
692
+
693
+ token = getToken(0);
694
+ if (token.getType() == Token.STRING &&
695
+ getToken(1).getType() == Token.SEMI) {
696
+ // This is a hint. Hints are empty statements that look like
697
+ // "localvar1:nomunge, localvar2:nomunge"; They allow developers
698
+ // to prevent specific symbols from getting obfuscated (some heretic
699
+ // implementations, such as Prototype 1.6, require specific variable
700
+ // names, such as $super for example, in order to work appropriately.
701
+ // Note: right now, only "nomunge" is supported in the right hand side
702
+ // of a hint. However, in the future, the right hand side may contain
703
+ // other values.
704
+ consumeToken();
705
+ String hints = token.getValue();
706
+ // Remove the leading and trailing quotes...
707
+ hints = hints.substring(1, hints.length() - 1).trim();
708
+ StringTokenizer st1 = new StringTokenizer(hints, ",");
709
+ while (st1.hasMoreTokens()) {
710
+ String hint = st1.nextToken();
711
+ int idx = hint.indexOf(':');
712
+ if (idx <= 0 || idx >= hint.length() - 1) {
713
+ if (mode == BUILDING_SYMBOL_TREE) {
714
+ // No need to report the error twice, hence the test...
715
+ warn("Invalid hint syntax: " + hint, true);
716
+ }
717
+ break;
718
+ }
719
+ String variableName = hint.substring(0, idx).trim();
720
+ String variableType = hint.substring(idx + 1).trim();
721
+ if (mode == BUILDING_SYMBOL_TREE) {
722
+ fnScope.addHint(variableName, variableType);
723
+ } else if (mode == CHECKING_SYMBOL_TREE) {
724
+ identifier = fnScope.getIdentifier(variableName);
725
+ if (identifier != null) {
726
+ if (variableType.equals("nomunge")) {
727
+ identifier.preventMunging();
728
+ } else {
729
+ warn("Unsupported hint value: " + hint, true);
730
+ }
731
+ } else {
732
+ warn("Hint refers to an unknown identifier: " + hint, true);
733
+ }
734
+ }
735
+ }
736
+ }
737
+
738
+ parseScope(fnScope);
739
+ }
740
+
741
+ private void parseCatch() {
742
+
743
+ String symbol;
744
+ JavaScriptToken token;
745
+ ScriptOrFnScope currentScope;
746
+ JavaScriptIdentifier identifier;
747
+
748
+ token = getToken(-1);
749
+ assert token.getType() == Token.CATCH;
750
+ token = consumeToken();
751
+ assert token.getType() == Token.LP;
752
+ token = consumeToken();
753
+ assert token.getType() == Token.NAME;
754
+
755
+ symbol = token.getValue();
756
+ currentScope = getCurrentScope();
757
+
758
+ if (mode == BUILDING_SYMBOL_TREE) {
759
+ // We must declare the exception identifier in the containing function
760
+ // scope to avoid errors related to the obfuscation process. No need to
761
+ // display a warning if the symbol was already declared here...
762
+ currentScope.declareIdentifier(symbol);
763
+ } else {
764
+ identifier = getIdentifier(symbol, currentScope);
765
+ identifier.incrementRefcount();
766
+ }
767
+
768
+ token = consumeToken();
769
+ assert token.getType() == Token.RP;
770
+ }
771
+
772
+ private void parseExpression() {
773
+
774
+ // Parse the expression until we encounter a comma or a semi-colon
775
+ // in the same brace nesting, bracket nesting and paren nesting.
776
+ // Parse functions if any...
777
+
778
+ String symbol;
779
+ JavaScriptToken token;
780
+ ScriptOrFnScope currentScope;
781
+ JavaScriptIdentifier identifier;
782
+
783
+ int expressionBraceNesting = braceNesting;
784
+ int bracketNesting = 0;
785
+ int parensNesting = 0;
786
+
787
+ int length = tokens.size();
788
+
789
+ while (offset < length) {
790
+
791
+ token = consumeToken();
792
+ currentScope = getCurrentScope();
793
+
794
+ switch (token.getType()) {
795
+
796
+ case Token.SEMI:
797
+ case Token.COMMA:
798
+ if (braceNesting == expressionBraceNesting &&
799
+ bracketNesting == 0 &&
800
+ parensNesting == 0) {
801
+ return;
802
+ }
803
+ break;
804
+
805
+ case Token.FUNCTION:
806
+ parseFunctionDeclaration();
807
+ break;
808
+
809
+ case Token.LC:
810
+ braceNesting++;
811
+ break;
812
+
813
+ case Token.RC:
814
+ braceNesting--;
815
+ assert braceNesting >= expressionBraceNesting;
816
+ break;
817
+
818
+ case Token.LB:
819
+ bracketNesting++;
820
+ break;
821
+
822
+ case Token.RB:
823
+ bracketNesting--;
824
+ break;
825
+
826
+ case Token.LP:
827
+ parensNesting++;
828
+ break;
829
+
830
+ case Token.RP:
831
+ parensNesting--;
832
+ break;
833
+
834
+ case Token.NAME:
835
+ symbol = token.getValue();
836
+
837
+ if (mode == BUILDING_SYMBOL_TREE) {
838
+
839
+ if (symbol.equals("eval")) {
840
+
841
+ protectScopeFromObfuscation(currentScope);
842
+ warn("Using 'eval' is not recommended." + (munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
843
+
844
+ }
845
+
846
+ } else if (mode == CHECKING_SYMBOL_TREE) {
847
+
848
+ if ((offset < 2 ||
849
+ (getToken(-2).getType() != Token.DOT &&
850
+ getToken(-2).getType() != Token.GET &&
851
+ getToken(-2).getType() != Token.SET)) &&
852
+ getToken(0).getType() != Token.OBJECTLIT) {
853
+
854
+ identifier = getIdentifier(symbol, currentScope);
855
+
856
+ if (identifier == null) {
857
+
858
+ if (symbol.length() <= 3 && !builtin.contains(symbol)) {
859
+ // Here, we found an undeclared and un-namespaced symbol that is
860
+ // 3 characters or less in length. Declare it in the global scope.
861
+ // We don't need to declare longer symbols since they won't cause
862
+ // any conflict with other munged symbols.
863
+ globalScope.declareIdentifier(symbol);
864
+ warn("Found an undeclared symbol: " + symbol, true);
865
+ }
866
+
867
+ } else {
868
+
869
+ identifier.incrementRefcount();
870
+ }
871
+ }
872
+ }
873
+ break;
874
+ }
875
+ }
876
+ }
877
+
878
+ private void parseScope(ScriptOrFnScope scope) {
879
+
880
+ String symbol;
881
+ JavaScriptToken token;
882
+ JavaScriptIdentifier identifier;
883
+
884
+ int length = tokens.size();
885
+
886
+ enterScope(scope);
887
+
888
+ while (offset < length) {
889
+
890
+ token = consumeToken();
891
+
892
+ switch (token.getType()) {
893
+
894
+ case Token.VAR:
895
+
896
+ if (mode == BUILDING_SYMBOL_TREE && scope.incrementVarCount() > 1) {
897
+ warn("Try to use a single 'var' statement per scope.", true);
898
+ }
899
+
900
+ /* FALLSTHROUGH */
901
+
902
+ case Token.CONST:
903
+
904
+ // The var keyword is followed by at least one symbol name.
905
+ // If several symbols follow, they are comma separated.
906
+ for (; ;) {
907
+ token = consumeToken();
908
+
909
+ assert token.getType() == Token.NAME;
910
+
911
+ if (mode == BUILDING_SYMBOL_TREE) {
912
+ symbol = token.getValue();
913
+ if (scope.getIdentifier(symbol) == null) {
914
+ scope.declareIdentifier(symbol);
915
+ } else {
916
+ warn("The variable " + symbol + " has already been declared in the same scope...", true);
917
+ }
918
+ }
919
+
920
+ token = getToken(0);
921
+
922
+ assert token.getType() == Token.SEMI ||
923
+ token.getType() == Token.ASSIGN ||
924
+ token.getType() == Token.COMMA ||
925
+ token.getType() == Token.IN;
926
+
927
+ if (token.getType() == Token.IN) {
928
+ break;
929
+ } else {
930
+ parseExpression();
931
+ token = getToken(-1);
932
+ if (token.getType() == Token.SEMI) {
933
+ break;
934
+ }
935
+ }
936
+ }
937
+ break;
938
+
939
+ case Token.FUNCTION:
940
+ parseFunctionDeclaration();
941
+ break;
942
+
943
+ case Token.LC:
944
+ braceNesting++;
945
+ break;
946
+
947
+ case Token.RC:
948
+ braceNesting--;
949
+ assert braceNesting >= scope.getBraceNesting();
950
+ if (braceNesting == scope.getBraceNesting()) {
951
+ leaveCurrentScope();
952
+ return;
953
+ }
954
+ break;
955
+
956
+ case Token.WITH:
957
+ if (mode == BUILDING_SYMBOL_TREE) {
958
+ // Inside a 'with' block, it is impossible to figure out
959
+ // statically whether a symbol is a local variable or an
960
+ // object member. As a consequence, the only thing we can
961
+ // do is turn the obfuscation off for the highest scope
962
+ // containing the 'with' block.
963
+ protectScopeFromObfuscation(scope);
964
+ warn("Using 'with' is not recommended." + (munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
965
+ }
966
+ break;
967
+
968
+ case Token.CATCH:
969
+ parseCatch();
970
+ break;
971
+
972
+ case Token.NAME:
973
+ symbol = token.getValue();
974
+
975
+ if (mode == BUILDING_SYMBOL_TREE) {
976
+
977
+ if (symbol.equals("eval")) {
978
+
979
+ protectScopeFromObfuscation(scope);
980
+ warn("Using 'eval' is not recommended." + (munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
981
+
982
+ }
983
+
984
+ } else if (mode == CHECKING_SYMBOL_TREE) {
985
+
986
+ if ((offset < 2 || getToken(-2).getType() != Token.DOT) &&
987
+ getToken(0).getType() != Token.OBJECTLIT) {
988
+
989
+ identifier = getIdentifier(symbol, scope);
990
+
991
+ if (identifier == null) {
992
+
993
+ if (symbol.length() <= 3 && !builtin.contains(symbol)) {
994
+ // Here, we found an undeclared and un-namespaced symbol that is
995
+ // 3 characters or less in length. Declare it in the global scope.
996
+ // We don't need to declare longer symbols since they won't cause
997
+ // any conflict with other munged symbols.
998
+ globalScope.declareIdentifier(symbol);
999
+ warn("Found an undeclared symbol: " + symbol, true);
1000
+ }
1001
+
1002
+ } else {
1003
+
1004
+ identifier.incrementRefcount();
1005
+ }
1006
+ }
1007
+ }
1008
+ break;
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ private void buildSymbolTree() {
1014
+ offset = 0;
1015
+ braceNesting = 0;
1016
+ scopes.clear();
1017
+ indexedScopes.clear();
1018
+ indexedScopes.put(new Integer(0), globalScope);
1019
+ mode = BUILDING_SYMBOL_TREE;
1020
+ parseScope(globalScope);
1021
+ }
1022
+
1023
+ private void mungeSymboltree() {
1024
+
1025
+ if (!munge) {
1026
+ return;
1027
+ }
1028
+
1029
+ // One problem with obfuscation resides in the use of undeclared
1030
+ // and un-namespaced global symbols that are 3 characters or less
1031
+ // in length. Here is an example:
1032
+ //
1033
+ // var declaredGlobalVar;
1034
+ //
1035
+ // function declaredGlobalFn() {
1036
+ // var localvar;
1037
+ // localvar = abc; // abc is an undeclared global symbol
1038
+ // }
1039
+ //
1040
+ // In the example above, there is a slim chance that localvar may be
1041
+ // munged to 'abc', conflicting with the undeclared global symbol
1042
+ // abc, creating a potential bug. The following code detects such
1043
+ // global symbols. This must be done AFTER the entire file has been
1044
+ // parsed, and BEFORE munging the symbol tree. Note that declaring
1045
+ // extra symbols in the global scope won't hurt.
1046
+ //
1047
+ // Note: Since we go through all the tokens to do this, we also use
1048
+ // the opportunity to count how many times each identifier is used.
1049
+
1050
+ offset = 0;
1051
+ braceNesting = 0;
1052
+ scopes.clear();
1053
+ mode = CHECKING_SYMBOL_TREE;
1054
+ parseScope(globalScope);
1055
+ globalScope.munge();
1056
+ }
1057
+
1058
+ private StringBuffer printSymbolTree(int linebreakpos, boolean preserveAllSemiColons)
1059
+ throws IOException {
1060
+
1061
+ offset = 0;
1062
+ braceNesting = 0;
1063
+ scopes.clear();
1064
+
1065
+ String symbol;
1066
+ JavaScriptToken token;
1067
+ ScriptOrFnScope currentScope;
1068
+ JavaScriptIdentifier identifier;
1069
+
1070
+ int length = tokens.size();
1071
+ StringBuffer result = new StringBuffer();
1072
+
1073
+ int linestartpos = 0;
1074
+
1075
+ enterScope(globalScope);
1076
+
1077
+ while (offset < length) {
1078
+
1079
+ token = consumeToken();
1080
+ symbol = token.getValue();
1081
+ currentScope = getCurrentScope();
1082
+
1083
+ switch (token.getType()) {
1084
+
1085
+ case Token.NAME:
1086
+
1087
+ if (offset >= 2 && getToken(-2).getType() == Token.DOT ||
1088
+ getToken(0).getType() == Token.OBJECTLIT) {
1089
+
1090
+ result.append(symbol);
1091
+
1092
+ } else {
1093
+
1094
+ identifier = getIdentifier(symbol, currentScope);
1095
+ if (identifier != null) {
1096
+ if (identifier.getMungedValue() != null) {
1097
+ result.append(identifier.getMungedValue());
1098
+ } else {
1099
+ result.append(symbol);
1100
+ }
1101
+ if (currentScope != globalScope && identifier.getRefcount() == 0) {
1102
+ warn("The symbol " + symbol + " is declared but is apparently never used.\nThis code can probably be written in a more compact way.", true);
1103
+ }
1104
+ } else {
1105
+ result.append(symbol);
1106
+ }
1107
+ }
1108
+ break;
1109
+
1110
+ case Token.REGEXP:
1111
+ case Token.NUMBER:
1112
+ case Token.STRING:
1113
+ result.append(symbol);
1114
+ break;
1115
+
1116
+ case Token.ADD:
1117
+ case Token.SUB:
1118
+ result.append((String) literals.get(new Integer(token.getType())));
1119
+ if (offset < length) {
1120
+ token = getToken(0);
1121
+ if (token.getType() == Token.INC ||
1122
+ token.getType() == Token.DEC ||
1123
+ token.getType() == Token.ADD ||
1124
+ token.getType() == Token.DEC) {
1125
+ // Handle the case x +/- ++/-- y
1126
+ // We must keep a white space here. Otherwise, x +++ y would be
1127
+ // interpreted as x ++ + y by the compiler, which is a bug (due
1128
+ // to the implicit assignment being done on the wrong variable)
1129
+ result.append(' ');
1130
+ } else if (token.getType() == Token.POS && getToken(-1).getType() == Token.ADD ||
1131
+ token.getType() == Token.NEG && getToken(-1).getType() == Token.SUB) {
1132
+ // Handle the case x + + y and x - - y
1133
+ result.append(' ');
1134
+ }
1135
+ }
1136
+ break;
1137
+
1138
+ case Token.FUNCTION:
1139
+ result.append("function");
1140
+ token = consumeToken();
1141
+ if (token.getType() == Token.NAME) {
1142
+ result.append(' ');
1143
+ symbol = token.getValue();
1144
+ identifier = getIdentifier(symbol, currentScope);
1145
+ assert identifier != null;
1146
+ if (identifier.getMungedValue() != null) {
1147
+ result.append(identifier.getMungedValue());
1148
+ } else {
1149
+ result.append(symbol);
1150
+ }
1151
+ if (currentScope != globalScope && identifier.getRefcount() == 0) {
1152
+ warn("The symbol " + symbol + " is declared but is apparently never used.\nThis code can probably be written in a more compact way.", true);
1153
+ }
1154
+ token = consumeToken();
1155
+ }
1156
+ assert token.getType() == Token.LP;
1157
+ result.append('(');
1158
+ currentScope = (ScriptOrFnScope) indexedScopes.get(new Integer(offset));
1159
+ enterScope(currentScope);
1160
+ while ((token = consumeToken()).getType() != Token.RP) {
1161
+ assert token.getType() == Token.NAME || token.getType() == Token.COMMA;
1162
+ if (token.getType() == Token.NAME) {
1163
+ symbol = token.getValue();
1164
+ identifier = getIdentifier(symbol, currentScope);
1165
+ assert identifier != null;
1166
+ if (identifier.getMungedValue() != null) {
1167
+ result.append(identifier.getMungedValue());
1168
+ } else {
1169
+ result.append(symbol);
1170
+ }
1171
+ } else if (token.getType() == Token.COMMA) {
1172
+ result.append(',');
1173
+ }
1174
+ }
1175
+ result.append(')');
1176
+ token = consumeToken();
1177
+ assert token.getType() == Token.LC;
1178
+ result.append('{');
1179
+ braceNesting++;
1180
+ token = getToken(0);
1181
+ if (token.getType() == Token.STRING &&
1182
+ getToken(1).getType() == Token.SEMI) {
1183
+ // This is a hint. Skip it!
1184
+ consumeToken();
1185
+ consumeToken();
1186
+ }
1187
+ break;
1188
+
1189
+ case Token.RETURN:
1190
+ case Token.TYPEOF:
1191
+ result.append(literals.get(new Integer(token.getType())));
1192
+ // No space needed after 'return' and 'typeof' when followed
1193
+ // by '(', '[', '{', a string or a regexp.
1194
+ if (offset < length) {
1195
+ token = getToken(0);
1196
+ if (token.getType() != Token.LP &&
1197
+ token.getType() != Token.LB &&
1198
+ token.getType() != Token.LC &&
1199
+ token.getType() != Token.STRING &&
1200
+ token.getType() != Token.REGEXP &&
1201
+ token.getType() != Token.SEMI) {
1202
+ result.append(' ');
1203
+ }
1204
+ }
1205
+ break;
1206
+
1207
+ case Token.CASE:
1208
+ case Token.THROW:
1209
+ result.append(literals.get(new Integer(token.getType())));
1210
+ // White-space needed after 'case' and 'throw' when not followed by a string.
1211
+ if (offset < length && getToken(0).getType() != Token.STRING) {
1212
+ result.append(' ');
1213
+ }
1214
+ break;
1215
+
1216
+ case Token.BREAK:
1217
+ case Token.CONTINUE:
1218
+ result.append(literals.get(new Integer(token.getType())));
1219
+ if (offset < length && getToken(0).getType() != Token.SEMI) {
1220
+ // If 'break' or 'continue' is not followed by a semi-colon, it must
1221
+ // be followed by a label, hence the need for a white space.
1222
+ result.append(' ');
1223
+ }
1224
+ break;
1225
+
1226
+ case Token.LC:
1227
+ result.append('{');
1228
+ braceNesting++;
1229
+ break;
1230
+
1231
+ case Token.RC:
1232
+ result.append('}');
1233
+ braceNesting--;
1234
+ assert braceNesting >= currentScope.getBraceNesting();
1235
+ if (braceNesting == currentScope.getBraceNesting()) {
1236
+ leaveCurrentScope();
1237
+ }
1238
+ break;
1239
+
1240
+ case Token.SEMI:
1241
+ // No need to output a semi-colon if the next character is a right-curly...
1242
+ if (preserveAllSemiColons || offset < length && getToken(0).getType() != Token.RC) {
1243
+ result.append(';');
1244
+ }
1245
+
1246
+ if (linebreakpos >= 0 && result.length() - linestartpos > linebreakpos) {
1247
+ // Some source control tools don't like it when files containing lines longer
1248
+ // than, say 8000 characters, are checked in. The linebreak option is used in
1249
+ // that case to split long lines after a specific column.
1250
+ result.append('\n');
1251
+ linestartpos = result.length();
1252
+ }
1253
+ break;
1254
+
1255
+ default:
1256
+ String literal = (String) literals.get(new Integer(token.getType()));
1257
+ if (literal != null) {
1258
+ result.append(literal);
1259
+ } else {
1260
+ warn("This symbol cannot be printed: " + symbol, true);
1261
+ }
1262
+ break;
1263
+ }
1264
+ }
1265
+
1266
+ // Append a semi-colon at the end, even if unnecessary semi-colons are
1267
+ // supposed to be removed. This is especially useful when concatenating
1268
+ // several minified files (the absence of an ending semi-colon at the
1269
+ // end of one file may very likely cause a syntax error)
1270
+ if (!preserveAllSemiColons &&
1271
+ result.length() > 0) {
1272
+ if (result.charAt(result.length() - 1) == '\n') {
1273
+ result.setCharAt(result.length() - 1, ';');
1274
+ } else {
1275
+ result.append(';');
1276
+ }
1277
+ }
1278
+
1279
+ return result;
1280
+ }
1281
+ }