-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
GwtRunner.java
354 lines (319 loc) · 11.9 KB
/
GwtRunner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/*
* Copyright 2016 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.gwt.client;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.javascript.jscomp.BasicErrorManager;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.SourceMapInput;
import com.google.javascript.jscomp.WarningLevel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* Runner for the GWT-compiled JSCompiler as a single exported method.
*/
public final class GwtRunner implements EntryPoint {
private static final CompilationLevel DEFAULT_COMPILATION_LEVEL =
CompilationLevel.SIMPLE_OPTIMIZATIONS;
private GwtRunner() {}
/**
* Specifies flags and their defaults.
*
* You must specify defaults in the constructor (as of August 2016). Defaults specified
* alongside fields will cause falsey values to be optimized and inlined. Fix here-
* https://gwt-review.googlesource.com/#/c/16600/
*/
@JsType(namespace = JsPackage.GLOBAL, name = "Flags")
public static class Flags {
public boolean angularPass;
public boolean assumeFunctionWrapper;
public String compilationLevel;
public boolean dartPass;
public boolean exportLocalPropertyDefinitions;
public boolean generateExports;
public String languageIn;
public String languageOut;
public boolean checksOnly;
public boolean newTypeInf;
public boolean polymerPass;
public boolean preserveTypeAnnotations;
public boolean processCommonJsModules;
public String renamePrefixNamespace;
public boolean rewritePolyfills;
public String warningLevel;
public boolean useTypesForOptimization;
// These flags do not match the Java jar release.
public File[] jsCode;
public File[] externs;
public boolean createSourceMap;
/**
* Flags constructor. Defaults must be specified here for every field.
*/
public Flags() {
this.angularPass = false;
this.assumeFunctionWrapper = false;
this.compilationLevel = "SIMPLE";
this.dartPass = false;
this.exportLocalPropertyDefinitions = false;
this.generateExports = false;
this.languageIn = "ES6";
this.languageOut = "ES5";
this.checksOnly = false;
this.newTypeInf = false;
this.polymerPass = false;
this.preserveTypeAnnotations = false;
this.processCommonJsModules = false;
this.renamePrefixNamespace = null;
this.rewritePolyfills = true;
this.warningLevel = "DEFAULT";
this.useTypesForOptimization = true;
this.jsCode = null;
this.externs = null;
this.createSourceMap = false;
}
/**
* Updates this {@link Flags} with a raw {@link JavaScriptObject}.
*
* @param raw The raw flags passed to this program.
* @return A list of invalid/unhandled flags.
*/
public native String[] update(JavaScriptObject raw) /*-{
var unhandled = [];
for (var k in raw) {
if (k in this) {
this[k] = raw[k];
} else {
unhandled.push(k);
}
}
return unhandled;
}-*/;
}
/**
* File object matching {@code AbstractCommandLineRunner.JsonFileSpec}. This is marked as the
* native {@code Object} type as it's not instantiated anywhere.
*/
@JsType(namespace = JsPackage.GLOBAL, name = "Object", isNative = true)
public static class File {
@JsProperty String path;
@JsProperty String src;
@JsProperty String sourceMap;
}
/**
* Output type returned to caller.
*/
@JsType(namespace = JsPackage.GLOBAL, name = "ModuleOutput")
public static class ModuleOutput {
@JsProperty String compiledCode;
@JsProperty String sourceMap;
@JsProperty JavaScriptObject[] errors;
@JsProperty JavaScriptObject[] warnings;
}
private static native JavaScriptObject createError(String file, String description, String type,
int lineNo, int charNo) /*-{
return {file: file, description: description, type: type, lineNo: lineNo, charNo: charNo};
}-*/;
/**
* Convert a list of {@link JSError} instances to a JS array containing plain objects.
*/
private static JavaScriptObject[] toNativeErrorArray(List<JSError> errors) {
JavaScriptObject out[] = new JavaScriptObject[errors.size()];
for (int i = 0; i < errors.size(); ++i) {
JSError error = errors.get(i);
DiagnosticType type = error.getType();
out[i] = createError(error.sourceName, error.description, type != null ? type.key : null,
error.lineNumber, error.getCharno());
}
return out;
}
private static void applyDefaultOptions(CompilerOptions options) {
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
WarningLevel.DEFAULT.setOptionsForWarningLevel(options);
options.setLanguageIn(LanguageMode.ECMASCRIPT6);
options.setLanguageOut(LanguageMode.ECMASCRIPT5);
}
private static void applyOptionsFromFlags(CompilerOptions options, Flags flags) {
CompilationLevel level = DEFAULT_COMPILATION_LEVEL;
if (flags.compilationLevel != null) {
level = CompilationLevel.fromString(flags.compilationLevel.toUpperCase());
if (level == null) {
throw new RuntimeException(
"Bad value for compilationLevel: " + flags.compilationLevel);
}
}
level.setOptionsForCompilationLevel(options);
if (flags.assumeFunctionWrapper) {
level.setWrappedOutputOptimizations(options);
}
if (flags.useTypesForOptimization) {
level.setTypeBasedOptimizationOptions(options);
}
WarningLevel warningLevel = WarningLevel.DEFAULT;
if (flags.warningLevel != null) {
warningLevel = WarningLevel.valueOf(flags.warningLevel);
}
warningLevel.setOptionsForWarningLevel(options);
LanguageMode languageIn = LanguageMode.fromString(flags.languageIn);
if (languageIn == null) {
throw new RuntimeException("Bad value for languageIn: " + flags.languageIn);
}
options.setLanguageIn(languageIn);
LanguageMode languageOut = LanguageMode.fromString(flags.languageOut);
if (languageOut == null) {
throw new RuntimeException("Bad value for languageOut: " + flags.languageOut);
}
options.setLanguageOut(languageOut);
if (flags.createSourceMap) {
options.setSourceMapOutputPath("%output%");
}
options.setAngularPass(flags.angularPass);
options.setChecksOnly(flags.checksOnly);
options.setDartPass(flags.dartPass);
options.setExportLocalPropertyDefinitions(flags.exportLocalPropertyDefinitions);
options.setGenerateExports(flags.generateExports);
options.setNewTypeInference(flags.newTypeInf);
options.setPolymerPass(flags.polymerPass);
options.setPreserveTypeAnnotations(flags.preserveTypeAnnotations);
options.setProcessCommonJSModules(flags.processCommonJsModules);
options.setRenamePrefixNamespace(flags.renamePrefixNamespace);
options.setRewritePolyfills(flags.rewritePolyfills);
}
private static void disableUnsupportedOptions(CompilerOptions options) {
options.getDependencyOptions().setDependencySorting(false);
}
private static List<SourceFile> fromFileArray(File[] src, String unknownPrefix) {
List<SourceFile> out = new ArrayList<>();
if (src != null) {
for (int i = 0; i < src.length; ++i) {
File file = src[i];
String path = file.path;
if (path == null) {
path = unknownPrefix + i;
}
out.add(SourceFile.fromCode(path, nullToEmpty(file.src)));
}
}
return ImmutableList.copyOf(out);
}
private static ImmutableMap<String, SourceMapInput> buildSourceMaps(
File[] src, String unknownPrefix) {
ImmutableMap.Builder<String, SourceMapInput> inputSourceMaps = new ImmutableMap.Builder<>();
if (src != null) {
for (int i = 0; i < src.length; ++i) {
File file = src[i];
if (isNullOrEmpty(file.sourceMap)) {
continue;
}
String path = file.path;
if (path == null) {
path = unknownPrefix + i;
}
path += ".map";
SourceFile sf = SourceFile.fromCode(path, file.sourceMap);
inputSourceMaps.put(path, new SourceMapInput(sf));
}
}
return inputSourceMaps.build();
}
/**
* Public compiler call. Exposed in {@link #exportCompile}.
*
* @param raw The passed raw flags from the user.
* @return The output from the compile.
*/
public static ModuleOutput compile(JavaScriptObject raw) {
Flags flags = new Flags();
String[] unhandled = flags.update(raw);
if (unhandled.length > 0) {
throw new RuntimeException("Unhandled flag: " + unhandled[0]);
}
List<SourceFile> externs = fromFileArray(flags.externs, "Extern_");
List<SourceFile> jsCode = fromFileArray(flags.jsCode, "Input_");
ImmutableMap<String, SourceMapInput> sourceMaps = buildSourceMaps(flags.jsCode, "Input_");
CompilerOptions options = new CompilerOptions();
applyDefaultOptions(options);
applyOptionsFromFlags(options, flags);
options.setInputSourceMaps(sourceMaps);
disableUnsupportedOptions(options);
NodeErrorManager errorManager = new NodeErrorManager();
Compiler compiler = new Compiler();
compiler.setErrorManager(errorManager);
compiler.compile(externs, jsCode, options);
ModuleOutput output = new ModuleOutput();
output.compiledCode = compiler.toSource();
output.errors = toNativeErrorArray(errorManager.errors);
output.warnings = toNativeErrorArray(errorManager.warnings);
if (flags.createSourceMap) {
StringBuilder b = new StringBuilder();
try {
compiler.getSourceMap().appendTo(b, "IGNORED");
} catch (IOException e) {
// ignore
}
output.sourceMap = b.toString();
}
return output;
}
/**
* Exports the {@link #compile} method via JSNI.
*
* This will be placed on {@code module.exports} or {@code this}.
*/
public native void exportCompile() /*-{
var fn = $entry(@com.google.javascript.jscomp.gwt.client.GwtRunner::compile(*));
if (typeof module !== 'undefined' && module.exports) {
module.exports = fn;
} else {
this.compile = fn;
}
}-*/;
@Override
public void onModuleLoad() {
exportCompile();
}
/**
* Custom {@link BasicErrorManager} to record {@link JSError} instances.
*/
private static class NodeErrorManager extends BasicErrorManager {
final List<JSError> errors = new ArrayList<>();
final List<JSError> warnings = new ArrayList<>();
@Override
public void println(CheckLevel level, JSError error) {
if (level == CheckLevel.ERROR) {
errors.add(error);
} else if (level == CheckLevel.WARNING) {
warnings.add(error);
}
}
@Override
public void printSummary() {}
}
}