golf 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +21 -0
- data/Rakefile +12 -0
- data/bin/golf +8 -0
- data/golf-java/README.markdown +41 -0
- data/golf-java/THANKS.markdown +14 -0
- data/golf-java/build/classes.zip +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfResource$MimeMapping$MimeMappingSingleton.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfResource$MimeMapping.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfResource.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$1.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfContext.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfParams.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$GolfSession.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$PermanentRedirectException.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$RedirectException.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet$StoredJSVM.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/GolfServlet.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/JsonpTunnel.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/Main$1.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/Main$RingList.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/Main.class +0 -0
- data/golf-java/build/com/thinkminimo/golf/ProxyServlet.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/CssCompressor.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/JarClassLoader.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptCompressor.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/JavaScriptToken.class +0 -0
- data/golf-java/build/com/yahoo/platform/yui/compressor/ScriptOrFnScope.class +0 -0
- data/golf-java/build/depends.zip +0 -0
- data/golf-java/build/golf +2 -0
- data/golf-java/build/org/json/JSONArray.class +0 -0
- data/golf-java/build/org/json/JSONException.class +0 -0
- data/golf-java/build/org/json/JSONObject$1.class +0 -0
- data/golf-java/build/org/json/JSONObject$Null.class +0 -0
- data/golf-java/build/org/json/JSONObject.class +0 -0
- data/golf-java/build/org/json/JSONString.class +0 -0
- data/golf-java/build/org/json/JSONStringer.class +0 -0
- data/golf-java/build/org/json/JSONTokener.class +0 -0
- data/golf-java/build/org/json/JSONWriter.class +0 -0
- data/golf-java/build/resources.zip +0 -0
- data/golf-java/build.xml +174 -0
- data/golf-java/dist/golf.zip +0 -0
- data/golf-java/lib/ant-launcher.jar +0 -0
- data/golf-java/lib/ant.jar +0 -0
- data/golf-java/lib/commons-codec-1.4.jar +0 -0
- data/golf-java/lib/commons-collections-3.2.1.jar +0 -0
- data/golf-java/lib/commons-fileupload-1.2.1.jar +0 -0
- data/golf-java/lib/commons-httpclient-3.1.jar +0 -0
- data/golf-java/lib/commons-io-1.4.jar +0 -0
- data/golf-java/lib/commons-lang-2.4.jar +0 -0
- data/golf-java/lib/commons-logging-1.1.1.jar +0 -0
- data/golf-java/lib/cssparser-0.9.5.jar +0 -0
- data/golf-java/lib/getopt-0.1-dev.jar +0 -0
- data/golf-java/lib/htmlunit-2.6.jar +0 -0
- data/golf-java/lib/htmlunit-core-js-2.6.jar +0 -0
- data/golf-java/lib/java-xmlbuilder-1.jar +0 -0
- data/golf-java/lib/jets3t-0.7.0.jar +0 -0
- data/golf-java/lib/jetty-6.1.15.jar +0 -0
- data/golf-java/lib/jetty-util-6.1.15.jar +0 -0
- data/golf-java/lib/log4j-1.2.15.jar +0 -0
- data/golf-java/lib/nekohtml-1.9.13.jar +0 -0
- data/golf-java/lib/sac-1.3.jar +0 -0
- data/golf-java/lib/serializer-2.7.1.jar +0 -0
- data/golf-java/lib/servlet-api-2.5-20081211.jar +0 -0
- data/golf-java/lib/xalan-2.7.1.jar +0 -0
- data/golf-java/lib/xercesImpl-2.9.1.jar +0 -0
- data/golf-java/lib/xml-apis-1.3.04.jar +0 -0
- data/golf-java/resources/app_error.html +60 -0
- data/golf-java/resources/error.html +29 -0
- data/golf-java/resources/forcebot.txt +0 -0
- data/golf-java/resources/forceclient.txt +0 -0
- data/golf-java/resources/forceproxy.txt +0 -0
- data/golf-java/resources/head.html +0 -0
- data/golf-java/resources/jquery.address.js +439 -0
- data/golf-java/resources/jquery.golf.js +945 -0
- data/golf-java/resources/jquery.js +4376 -0
- data/golf-java/resources/jsdetect.html +23 -0
- data/golf-java/resources/loading.gif +0 -0
- data/golf-java/resources/new.html +45 -0
- data/golf-java/resources/new.static.html +45 -0
- data/golf-java/resources/noscript.forceclient.html +11 -0
- data/golf-java/resources/noscript.html +18 -0
- data/golf-java/resources/noscript.static.html +15 -0
- data/golf-java/resources/project.xml +21 -0
- data/golf-java/resources/proxy_project.xml +11 -0
- data/golf-java/resources/proxy_web.xml +40 -0
- data/golf-java/resources/static_project.xml +37 -0
- data/golf-java/resources/version +1 -0
- data/golf-java/resources/web.xml +36 -0
- data/golf-java/resources/welcome2golf.html +50 -0
- data/golf-java/src/com/thinkminimo/golf/GolfResource.java +582 -0
- data/golf-java/src/com/thinkminimo/golf/GolfServlet.java +1055 -0
- data/golf-java/src/com/thinkminimo/golf/JsonpTunnel.java +86 -0
- data/golf-java/src/com/thinkminimo/golf/Main.java +1299 -0
- data/golf-java/src/com/thinkminimo/golf/ProxyServlet.java +577 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/CssCompressor.java +188 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/JarClassLoader.java +158 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptCompressor.java +1281 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.java +55 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/JavaScriptToken.java +28 -0
- data/golf-java/src/com/yahoo/platform/yui/compressor/ScriptOrFnScope.java +160 -0
- data/golf-java/src/org/json/JSONArray.java +934 -0
- data/golf-java/src/org/json/JSONException.java +27 -0
- data/golf-java/src/org/json/JSONObject.java +1550 -0
- data/golf-java/src/org/json/JSONString.java +18 -0
- data/golf-java/src/org/json/JSONStringer.java +78 -0
- data/golf-java/src/org/json/JSONTokener.java +422 -0
- data/golf-java/src/org/json/JSONWriter.java +323 -0
- data/golf-java/test/client/golftest/README.markdown +3 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.css +114 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.html +17 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.js +81 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/asc.gif +0 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/bg.gif +0 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/desc.gif +0 -0
- data/golf-java/test/client/golftest/components/com/thinkminimo/golf/test/Harness.res/test/test.html +1 -0
- data/golf-java/test/client/golftest/controller.js +131 -0
- data/golf-java/test/client/golftest/forceclient.txt +0 -0
- data/golf-java/test/client/golftest/forceproxy.txt +0 -0
- data/golf-java/test/client/golftest/head.html +1 -0
- data/golf-java/test/client/golftest/scripts/jquery.tablesort.js +75 -0
- data/golf-java/test/client/golftest/styles/style.css +18 -0
- data/golf-java/test/client/golftest/test/test.html +1 -0
- data/golf.gemspec +24 -0
- data/lib/golf/cli.rb +32 -0
- data/lib/golf/compiler.rb +12 -0
- data/lib/golf/rack.rb +17 -0
- data/lib/golf/version.rb +3 -0
- data/lib/golf.rb +6 -0
- data/template/Gemfile +5 -0
- data/template/config.ru +7 -0
- data/template/golfapp/components/Test/Test.html +22 -0
- data/template/golfapp/components/Test/Test.res/myfile.txt +1 -0
- data/template/golfapp/controller.js +10 -0
- data/template/golfapp/plugins/mylib.js +12 -0
- data/test/test_compiler.rb +10 -0
- data/test/test_helper.rb +6 -0
- data/test/test_rack.rb +17 -0
- 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
|
+
}
|