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

Side by Side Diff: third_party/closure_compiler/checker.py

Issue 1023253003: Revert of Python readability review for dbeam@. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | third_party/closure_compiler/compile_js.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Runs Closure compiler on a JavaScript file to check for errors.""" 6 """Runs Closure compiler on a JavaScript file to check for errors."""
7 7
8 import argparse 8 import argparse
9 import os 9 import os
10 import re 10 import re
11 import subprocess 11 import subprocess
12 import sys 12 import sys
13 import tempfile 13 import tempfile
14 14
15 import build.inputs 15 import build.inputs
16 import processor 16 import processor
17 import error_filter 17 import error_filter
18 18
19 19
20 class Checker(object): 20 class Checker(object):
21 """Runs the Closure compiler on a given source file to typecheck it.""" 21 """Runs the Closure compiler on a given source file and returns the
22 success/errors."""
22 23
23 _COMMON_CLOSURE_ARGS = [ 24 _COMMON_CLOSURE_ARGS = [
24 "--accept_const_keyword", 25 "--accept_const_keyword",
25 "--jscomp_error=accessControls", 26 "--jscomp_error=accessControls",
26 "--jscomp_error=ambiguousFunctionDecl", 27 "--jscomp_error=ambiguousFunctionDecl",
27 "--jscomp_error=checkStructDictInheritance", 28 "--jscomp_error=checkStructDictInheritance",
28 "--jscomp_error=checkTypes", 29 "--jscomp_error=checkTypes",
29 "--jscomp_error=checkVars", 30 "--jscomp_error=checkVars",
30 "--jscomp_error=constantProperty", 31 "--jscomp_error=constantProperty",
31 "--jscomp_error=deprecated", 32 "--jscomp_error=deprecated",
32 "--jscomp_error=externsValidation", 33 "--jscomp_error=externsValidation",
33 "--jscomp_error=globalThis", 34 "--jscomp_error=globalThis",
34 "--jscomp_error=invalidCasts", 35 "--jscomp_error=invalidCasts",
35 "--jscomp_error=missingProperties", 36 "--jscomp_error=missingProperties",
36 "--jscomp_error=missingReturn", 37 "--jscomp_error=missingReturn",
37 "--jscomp_error=nonStandardJsDocs", 38 "--jscomp_error=nonStandardJsDocs",
38 "--jscomp_error=suspiciousCode", 39 "--jscomp_error=suspiciousCode",
39 "--jscomp_error=undefinedNames", 40 "--jscomp_error=undefinedNames",
40 "--jscomp_error=undefinedVars", 41 "--jscomp_error=undefinedVars",
41 "--jscomp_error=unknownDefines", 42 "--jscomp_error=unknownDefines",
42 "--jscomp_error=uselessCode", 43 "--jscomp_error=uselessCode",
43 "--jscomp_error=visibility", 44 "--jscomp_error=visibility",
44 "--language_in=ECMASCRIPT5_STRICT", 45 "--language_in=ECMASCRIPT5_STRICT",
45 "--summary_detail_level=3", 46 "--summary_detail_level=3",
46 "--compilation_level=SIMPLE_OPTIMIZATIONS", 47 "--compilation_level=SIMPLE_OPTIMIZATIONS",
47 "--source_map_format=V3", 48 "--source_map_format=V3",
48 ] 49 ]
49 50
50 # These are the extra flags used when compiling in strict mode. 51 # These are the extra flags used when compiling in 'strict' mode.
51 # Flags that are normally disabled are turned on for strict mode. 52 # Flags that are normally disabled are turned on for strict mode.
52 _STRICT_CLOSURE_ARGS = [ 53 _STRICT_CLOSURE_ARGS = [
53 "--jscomp_error=reportUnknownTypes", 54 "--jscomp_error=reportUnknownTypes",
54 "--jscomp_error=duplicate", 55 "--jscomp_error=duplicate",
55 "--jscomp_error=misplacedTypeAnnotation", 56 "--jscomp_error=misplacedTypeAnnotation",
56 ] 57 ]
57 58
58 _DISABLED_CLOSURE_ARGS = [ 59 _DISABLED_CLOSURE_ARGS = [
59 # TODO(dbeam): happens when the same file is <include>d multiple times. 60 # TODO(dbeam): happens when the same file is <include>d multiple times.
60 "--jscomp_off=duplicate", 61 "--jscomp_off=duplicate",
61 # TODO(fukino): happens when cr.defineProperty() has a type annotation. 62 # TODO(fukino): happens when cr.defineProperty() has a type annotation.
62 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562. 63 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562.
63 "--jscomp_off=misplacedTypeAnnotation", 64 "--jscomp_off=misplacedTypeAnnotation",
64 ] 65 ]
65 66
66 _JAR_COMMAND = [ 67 _JAR_COMMAND = [
67 "java", 68 "java",
68 "-jar", 69 "-jar",
69 "-Xms1024m", 70 "-Xms1024m",
70 "-client", 71 "-client",
71 "-XX:+TieredCompilation" 72 "-XX:+TieredCompilation"
72 ] 73 ]
73 74
75 _found_java = False
76
74 def __init__(self, verbose=False, strict=False): 77 def __init__(self, verbose=False, strict=False):
75 """
76 Args:
77 verbose: Whether this class should output diagnostic messages.
78 strict: Whether the Closure Compiler should be invoked more strictly.
79 """
80 current_dir = os.path.join(os.path.dirname(__file__)) 78 current_dir = os.path.join(os.path.dirname(__file__))
81 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") 79 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar")
82 self._temp_files = [] 80 self._temp_files = []
83 self._verbose = verbose 81 self._verbose = verbose
84 self._strict = strict 82 self._strict = strict
85 self._error_filter = error_filter.PromiseErrorFilter() 83 self._error_filter = error_filter.PromiseErrorFilter()
86 84
87 def _clean_up(self): 85 def _clean_up(self):
88 """Deletes any temp files this class knows about."""
89 if not self._temp_files: 86 if not self._temp_files:
90 return 87 return
91 88
92 self._log_debug("Deleting temp files: %s" % ", ".join(self._temp_files)) 89 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files))
93 for f in self._temp_files: 90 for f in self._temp_files:
94 os.remove(f) 91 os.remove(f)
95 self._temp_files = [] 92 self._temp_files = []
96 93
97 def _log_debug(self, msg, error=False): 94 def _debug(self, msg, error=False):
98 """Logs |msg| to stdout if --verbose/-v is passed when invoking this script.
99
100 Args:
101 msg: A debug message to log.
102 """
103 if self._verbose: 95 if self._verbose:
104 print "(INFO) %s" % msg 96 print "(INFO) %s" % msg
105 97
106 def _log_error(self, msg): 98 def _error(self, msg):
107 """Logs |msg| to stderr regardless of --flags.
108
109 Args:
110 msg: An error message to log.
111 """
112 print >> sys.stderr, "(ERROR) %s" % msg 99 print >> sys.stderr, "(ERROR) %s" % msg
100 self._clean_up()
113 101
114 def _common_args(self): 102 def _common_args(self):
115 """Returns an array of the common closure compiler args.""" 103 """Returns an array of the common closure compiler args."""
116 if self._strict: 104 if self._strict:
117 return self._COMMON_CLOSURE_ARGS + self._STRICT_CLOSURE_ARGS 105 return self._COMMON_CLOSURE_ARGS + self._STRICT_CLOSURE_ARGS
118 return self._COMMON_CLOSURE_ARGS + self._DISABLED_CLOSURE_ARGS 106 return self._COMMON_CLOSURE_ARGS + self._DISABLED_CLOSURE_ARGS
119 107
120 def _run_jar(self, jar, args): 108 def _run_command(self, cmd):
121 """Runs a .jar from the command line with arguments. 109 """Runs a shell command.
122 110
123 Args: 111 Args:
124 jar: A file path to a .jar file 112 cmd: A list of tokens to be joined into a shell command.
125 args: A list of command line arguments to be passed when running the .jar.
126 113
127 Return: 114 Return:
128 (exit_code, stderr) The exit code of the command (e.g. 0 for success) and 115 True if the exit code was 0, else False.
129 the stderr collected while running |jar| (as a string).
130 """ 116 """
131 shell_command = " ".join(self._JAR_COMMAND + [jar] + args) 117 cmd_str = " ".join(cmd)
132 self._log_debug("Running jar: %s" % shell_command) 118 self._debug("Running command: %s" % cmd_str)
133 119
134 devnull = open(os.devnull, "w") 120 devnull = open(os.devnull, "w")
135 kwargs = {"stdout": devnull, "stderr": subprocess.PIPE, "shell": True} 121 return subprocess.Popen(
136 process = subprocess.Popen(shell_command, **kwargs) 122 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True)
137 _, stderr = process.communicate()
138 return process.returncode, stderr
139 123
140 def _get_line_number(self, match): 124 def _check_java_path(self):
141 """When chrome is built, it preprocesses its JavaScript from: 125 """Checks that `java` is on the system path."""
126 if not self._found_java:
127 proc = self._run_command(["which", "java"])
128 proc.communicate()
129 if proc.returncode == 0:
130 self._found_java = True
131 else:
132 self._error("Cannot find java (`which java` => %s)" % proc.returncode)
142 133
143 <include src="blah.js"> 134 return self._found_java
144 alert(1);
145 135
146 to: 136 def _run_jar(self, jar, args=None):
137 args = args or []
138 self._check_java_path()
139 return self._run_command(self._JAR_COMMAND + [jar] + args)
147 140
148 /* contents of blah.js inlined */ 141 def _fix_line_number(self, match):
149 alert(1); 142 """Changes a line number from /tmp/file:300 to /orig/file:100.
150
151 Because Closure Compiler requires this inlining already be done (as
152 <include> isn't valid JavaScript), this script creates temporary files to
153 expand all the <include>s.
154
155 When type errors are hit in temporary files, a developer doesn't know the
156 original source location to fix. This method maps from /tmp/file:300 back to
157 /original/source/file:100 so fixing errors is faster for developers.
158 143
159 Args: 144 Args:
160 match: A re.MatchObject from matching against a line number regex. 145 match: A re.MatchObject from matching against a line number regex.
161 146
162 Returns: 147 Returns:
163 The fixed up /file and :line number. 148 The fixed up /file and :line number.
164 """ 149 """
165 real_file = self._processor.get_file_from_line(match.group(1)) 150 real_file = self._processor.get_file_from_line(match.group(1))
166 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) 151 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number)
167 152
168 def _filter_errors(self, errors): 153 def _fix_up_error(self, error):
169 """Removes some extraneous errors. For example, we ignore: 154 """Filter out irrelevant errors or fix line numbers.
170
171 Variable x first declared in /tmp/expanded/file
172
173 Because it's just a duplicated error (it'll only ever show up 2+ times).
174 We also ignore Promose-based errors:
175
176 found : function (VolumeInfo): (Promise<(DirectoryEntry|null)>|null)
177 required: (function (Promise<VolumeInfo>): ?|null|undefined)
178
179 as templates don't work with Promises in all cases yet. See
180 https://github.com/google/closure-compiler/issues/715 for details.
181 155
182 Args: 156 Args:
183 errors: A list of string errors extracted from Closure Compiler output. 157 error: A Closure compiler error (2 line string with error and source).
184 158
185 Return: 159 Return:
186 A slimmer, sleeker list of relevant errors (strings). 160 The fixed up error string (blank if it should be ignored).
187 """ 161 """
188 first_declared_in = lambda e: " first declared in " not in e 162 if " first declared in " in error:
189 return self._error_filter.filter(filter(first_declared_in, errors)) 163 # Ignore "Variable x first declared in /same/file".
164 return ""
190 165
191 def _fix_up_error(self, error):
192 """Reverse the effects that funky <include> preprocessing steps have on
193 errors messages.
194
195 Args:
196 error: A Closure compiler error (2 line string with error and source).
197
198 Return:
199 The fixed up error string.
200 """
201 expanded_file = self._expanded_file 166 expanded_file = self._expanded_file
202 fixed = re.sub("%s:(\d+)" % expanded_file, self._get_line_number, error) 167 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error)
203 return fixed.replace(expanded_file, os.path.abspath(self._file_arg)) 168 return fixed.replace(expanded_file, os.path.abspath(self._file_arg))
204 169
205 def _format_errors(self, errors): 170 def _format_errors(self, errors):
206 """Formats Closure compiler errors to easily spot compiler output. 171 """Formats Closure compiler errors to easily spot compiler output."""
207 172 errors = filter(None, errors)
208 Args:
209 errors: A list of strings extracted from the Closure compiler's output.
210
211 Returns:
212 A formatted output string.
213 """
214 contents = "\n## ".join("\n\n".join(errors).splitlines()) 173 contents = "\n## ".join("\n\n".join(errors).splitlines())
215 return "## %s" % contents if contents else "" 174 return "## %s" % contents if contents else ""
216 175
217 def _create_temp_file(self, contents): 176 def _create_temp_file(self, contents):
218 """Creates an owned temporary file with |contents|.
219
220 Args:
221 content: A string of the file contens to write to a temporary file.
222
223 Return:
224 The filepath of the newly created, written, and closed temporary file.
225 """
226 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: 177 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file:
227 self._temp_files.append(tmp_file.name) 178 self._temp_files.append(tmp_file.name)
228 tmp_file.write(contents) 179 tmp_file.write(contents)
229 return tmp_file.name 180 return tmp_file.name
230 181
231 def _run_js_check(self, sources, out_file=None, externs=None): 182 def _run_js_check(self, sources, out_file=None, externs=None):
232 """Check |sources| for type errors. 183 if not self._check_java_path():
184 return 1, ""
233 185
234 Args:
235 sources: Files to check.
236 externs: @extern files that inform the compiler about custom globals.
237
238 Returns:
239 (errors, stderr) A parsed list of errors (strings) found by the compiler
240 and the raw stderr (as a string).
241 """
242 args = ["--js=%s" % s for s in sources] 186 args = ["--js=%s" % s for s in sources]
243 187
244 if out_file: 188 if out_file:
245 args += ["--js_output_file=%s" % out_file] 189 args += ["--js_output_file=%s" % out_file]
246 args += ["--create_source_map=%s.map" % out_file] 190 args += ["--create_source_map=%s.map" % out_file]
247 191
248 if externs: 192 if externs:
249 args += ["--externs=%s" % e for e in externs] 193 args += ["--externs=%s" % e for e in externs]
250
251 args_file_content = " %s" % " ".join(self._common_args() + args) 194 args_file_content = " %s" % " ".join(self._common_args() + args)
252 self._log_debug("Args: %s" % args_file_content.strip()) 195 self._debug("Args: %s" % args_file_content.strip())
253 196
254 args_file = self._create_temp_file(args_file_content) 197 args_file = self._create_temp_file(args_file_content)
255 self._log_debug("Args file: %s" % args_file) 198 self._debug("Args file: %s" % args_file)
256 199
257 runner_args = ["--compiler-args-file=%s" % args_file] 200 runner_args = ["--compiler-args-file=%s" % args_file]
258 _, stderr = self._run_jar(self._runner_jar, runner_args) 201 runner_cmd = self._run_jar(self._runner_jar, args=runner_args)
202 _, stderr = runner_cmd.communicate()
259 203
260 errors = stderr.strip().split("\n\n") 204 errors = stderr.strip().split("\n\n")
261 maybe_summary = errors.pop() 205 self._debug("Summary: %s" % errors.pop())
262 206
263 if re.search(".*error.*warning.*typed", maybe_summary): 207 self._clean_up()
264 self._log_debug("Summary: %s" % maybe_summary)
265 else:
266 # Not a summary. Running the jar failed. Bail.
267 self._log_error(stderr)
268 self._clean_up()
269 sys.exit(1)
270 208
271 return errors, stderr 209 return errors, stderr
272 210
273 def check(self, source_file, out_file=None, depends=None, externs=None): 211 def check(self, source_file, out_file=None, depends=None, externs=None):
274 """Closure compiler |source_file| while checking for errors. 212 """Closure compile a file and check for errors.
275 213
276 Args: 214 Args:
277 source_file: A file to check. 215 source_file: A file to check.
278 out_file: A file where the compiled output is written to. 216 out_file: A file where the compiled output is written to.
279 depends: Files that |source_file| requires to run (e.g. earlier <script>). 217 depends: Other files that would be included with a <script> earlier in
280 externs: @extern files that inform the compiler about custom globals. 218 the page.
219 externs: @extern files that inform the compiler about custom globals.
281 220
282 Returns: 221 Returns:
283 (found_errors, stderr) A boolean indicating whether errors were found and 222 (has_errors, output) A boolean indicating if there were errors and the
284 the raw Closure compiler stderr (as a string). 223 Closure compiler output (as a string).
285 """ 224 """
286 self._log_debug("FILE: %s" % source_file) 225 depends = depends or []
226 externs = externs or set()
227
228 if not self._check_java_path():
229 return 1, ""
230
231 self._debug("FILE: %s" % source_file)
287 232
288 if source_file.endswith("_externs.js"): 233 if source_file.endswith("_externs.js"):
289 self._log_debug("Skipping externs: %s" % source_file) 234 self._debug("Skipping externs: %s" % source_file)
290 return 235 return
291 236
292 self._file_arg = source_file 237 self._file_arg = source_file
293 238
294 cwd, tmp_dir = os.getcwd(), tempfile.gettempdir() 239 tmp_dir = tempfile.gettempdir()
295 rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f) 240 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f)
296 241
297 depends = depends or []
298 includes = [rel_path(f) for f in depends + [source_file]] 242 includes = [rel_path(f) for f in depends + [source_file]]
299 contents = ['<include src="%s">' % i for i in includes] 243 contents = ['<include src="%s">' % i for i in includes]
300 meta_file = self._create_temp_file("\n".join(contents)) 244 meta_file = self._create_temp_file("\n".join(contents))
301 self._log_debug("Meta file: %s" % meta_file) 245 self._debug("Meta file: %s" % meta_file)
302 246
303 self._processor = processor.Processor(meta_file) 247 self._processor = processor.Processor(meta_file)
304 self._expanded_file = self._create_temp_file(self._processor.contents) 248 self._expanded_file = self._create_temp_file(self._processor.contents)
305 self._log_debug("Expanded file: %s" % self._expanded_file) 249 self._debug("Expanded file: %s" % self._expanded_file)
306 250
307 errors, stderr = self._run_js_check([self._expanded_file], 251 errors, stderr = self._run_js_check([self._expanded_file],
308 out_file=out_file, externs=externs) 252 out_file=out_file, externs=externs)
309 filtered_errors = self._filter_errors(errors)
310 fixed_errors = map(self._fix_up_error, filtered_errors)
311 output = self._format_errors(fixed_errors)
312 253
313 if fixed_errors: 254 # Filter out false-positive promise chain errors.
255 # See https://github.com/google/closure-compiler/issues/715 for details.
256 errors = self._error_filter.filter(errors);
257
258 output = self._format_errors(map(self._fix_up_error, errors))
259 if errors:
314 prefix = "\n" if output else "" 260 prefix = "\n" if output else ""
315 self._log_error("Error in: %s%s%s" % (source_file, prefix, output)) 261 self._error("Error in: %s%s%s" % (source_file, prefix, output))
316 elif output: 262 elif output:
317 self._log_debug("Output: %s" % output) 263 self._debug("Output: %s" % output)
318 264
319 self._clean_up() 265 return bool(errors), output
320 return bool(fixed_errors), stderr
321 266
322 def check_multiple(self, sources): 267 def check_multiple(self, sources):
323 """Closure compile a set of files and check for errors. 268 """Closure compile a set of files and check for errors.
324 269
325 Args: 270 Args:
326 sources: An array of files to check. 271 sources: An array of files to check.
327 272
328 Returns: 273 Returns:
329 (found_errors, stderr) A boolean indicating whether errors were found and 274 (has_errors, output) A boolean indicating if there were errors and the
330 the raw Closure Compiler stderr (as a string). 275 Closure compiler output (as a string).
331 """ 276 """
332 errors, stderr = self._run_js_check(sources, []) 277
333 self._clean_up() 278 errors, stderr = self._run_js_check(sources)
334 return bool(errors), stderr 279 return bool(errors), stderr
335 280
336
337 if __name__ == "__main__": 281 if __name__ == "__main__":
338 parser = argparse.ArgumentParser( 282 parser = argparse.ArgumentParser(
339 description="Typecheck JavaScript using Closure compiler") 283 description="Typecheck JavaScript using Closure compiler")
340 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, 284 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE,
341 help="Path to a source file to typecheck") 285 help="Path to a source file to typecheck")
342 single_file_group = parser.add_mutually_exclusive_group() 286 single_file_group = parser.add_mutually_exclusive_group()
343 single_file_group.add_argument("--single-file", dest="single_file", 287 single_file_group.add_argument("--single-file", dest="single_file",
344 action="store_true", 288 action="store_true",
345 help="Process each source file individually") 289 help="Process each source file individually")
346 single_file_group.add_argument("--no-single-file", dest="single_file", 290 single_file_group.add_argument("--no-single-file", dest="single_file",
(...skipping 18 matching lines...) Expand all
365 309
366 if opts.out_file: 310 if opts.out_file:
367 out_dir = os.path.dirname(opts.out_file) 311 out_dir = os.path.dirname(opts.out_file)
368 if not os.path.exists(out_dir): 312 if not os.path.exists(out_dir):
369 os.makedirs(out_dir) 313 os.makedirs(out_dir)
370 314
371 checker = Checker(verbose=opts.verbose, strict=opts.strict) 315 checker = Checker(verbose=opts.verbose, strict=opts.strict)
372 if opts.single_file: 316 if opts.single_file:
373 for source in opts.sources: 317 for source in opts.sources:
374 depends, externs = build.inputs.resolve_recursive_dependencies( 318 depends, externs = build.inputs.resolve_recursive_dependencies(
375 source, depends, externs) 319 source,
376 found_errors, _ = checker.check(source, out_file=opts.out_file, 320 depends,
377 depends=depends, externs=externs) 321 externs)
378 if found_errors: 322 has_errors, _ = checker.check(source, out_file=opts.out_file,
323 depends=depends, externs=externs)
324 if has_errors:
379 sys.exit(1) 325 sys.exit(1)
326
380 else: 327 else:
381 found_errors, stderr = checker.check_multiple(opts.sources) 328 has_errors, errors = checker.check_multiple(opts.sources)
382 if found_errors: 329 if has_errors:
383 print stderr 330 print errors
384 sys.exit(1) 331 sys.exit(1)
385 332
386 if opts.success_stamp: 333 if opts.success_stamp:
387 with open(opts.success_stamp, "w"): 334 with open(opts.success_stamp, 'w'):
388 os.utime(opts.success_stamp, None) 335 os.utime(opts.success_stamp, None)
OLDNEW
« no previous file with comments | « no previous file | third_party/closure_compiler/compile_js.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698