OLD | NEW |
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 JavaScript files to check for errors and produce | 6 """Runs Closure compiler on JavaScript files to check for errors and produce |
7 minified output.""" | 7 minified output.""" |
8 | 8 |
9 import argparse | 9 import argparse |
10 import os | 10 import os |
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
179 | 179 |
180 Return: | 180 Return: |
181 The filepath of the newly created, written, and closed temporary file. | 181 The filepath of the newly created, written, and closed temporary file. |
182 """ | 182 """ |
183 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: | 183 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: |
184 self._temp_files.append(tmp_file.name) | 184 self._temp_files.append(tmp_file.name) |
185 tmp_file.write(contents) | 185 tmp_file.write(contents) |
186 return tmp_file.name | 186 return tmp_file.name |
187 | 187 |
188 def _run_js_check(self, sources, out_file=None, externs=None, | 188 def _run_js_check(self, sources, out_file=None, externs=None, |
189 closure_args=None): | 189 runner_args=None, closure_args=None): |
190 """Check |sources| for type errors. | 190 """Check |sources| for type errors. |
191 | 191 |
192 Args: | 192 Args: |
193 sources: Files to check. | 193 sources: Files to check. |
194 out_file: A file where the compiled output is written to. | 194 out_file: A file where the compiled output is written to. |
195 externs: @extern files that inform the compiler about custom globals. | 195 externs: @extern files that inform the compiler about custom globals. |
| 196 runner_args: Arguments passed to runner.jar. |
196 closure_args: Arguments passed directly to the Closure compiler. | 197 closure_args: Arguments passed directly to the Closure compiler. |
197 | 198 |
198 Returns: | 199 Returns: |
199 (errors, stderr) A parsed list of errors (strings) found by the compiler | 200 (errors, stderr) A parsed list of errors (strings) found by the compiler |
200 and the raw stderr (as a string). | 201 and the raw stderr (as a string). |
201 """ | 202 """ |
202 args = ["--js=%s" % s for s in sources] | 203 args = ["--js=%s" % s for s in sources] |
203 | 204 |
204 if out_file: | 205 if out_file: |
205 out_dir = os.path.dirname(out_file) | 206 out_dir = os.path.dirname(out_file) |
206 if not os.path.exists(out_dir): | 207 if not os.path.exists(out_dir): |
207 os.makedirs(out_dir) | 208 os.makedirs(out_dir) |
208 args += ["--js_output_file=%s" % out_file] | 209 args += ["--js_output_file=%s" % out_file] |
209 args += ["--create_source_map=%s" % (self._MAP_FILE_FORMAT % out_file)] | 210 args += ["--create_source_map=%s" % (self._MAP_FILE_FORMAT % out_file)] |
210 | 211 |
211 args += ["--externs=%s" % e for e in externs or []] | 212 args += ["--externs=%s" % e for e in externs or []] |
212 | 213 |
213 closure_args = closure_args or [] | 214 closure_args = closure_args or [] |
214 closure_args += ["summary_detail_level=3"] | 215 closure_args += ["summary_detail_level=3"] |
215 args += ["--%s" % arg for arg in closure_args] | 216 args += ["--%s" % arg for arg in closure_args] |
216 | 217 |
217 args_file_content = " %s" % " ".join(args) | 218 args_file_content = " %s" % " ".join(args) |
218 self._log_debug("Args: %s" % args_file_content.strip()) | 219 self._log_debug("Args: %s" % args_file_content.strip()) |
219 | 220 |
220 args_file = self._create_temp_file(args_file_content) | 221 args_file = self._create_temp_file(args_file_content) |
221 self._log_debug("Args file: %s" % args_file) | 222 self._log_debug("Args file: %s" % args_file) |
222 | 223 |
223 runner_args = ["--compiler-args-file=%s" % args_file] | 224 processed_runner_args = ["--%s" % arg for arg in runner_args or []] |
224 _, stderr = self._run_jar(self._runner_jar, runner_args) | 225 processed_runner_args += ["--compiler-args-file=%s" % args_file] |
| 226 _, stderr = self._run_jar(self._runner_jar, processed_runner_args) |
225 | 227 |
226 errors = stderr.strip().split("\n\n") | 228 errors = stderr.strip().split("\n\n") |
227 maybe_summary = errors.pop() | 229 maybe_summary = errors.pop() |
228 | 230 |
229 if re.search(".*error.*warning.*typed", maybe_summary): | 231 if re.search(".*error.*warning.*typed", maybe_summary): |
230 self._log_debug("Summary: %s" % maybe_summary) | 232 self._log_debug("Summary: %s" % maybe_summary) |
231 else: | 233 else: |
232 # Not a summary. Running the jar failed. Bail. | 234 # Not a summary. Running the jar failed. Bail. |
233 self._log_error(stderr) | 235 self._log_error(stderr) |
234 self._nuke_temp_files() | 236 self._nuke_temp_files() |
235 sys.exit(1) | 237 sys.exit(1) |
236 | 238 |
237 if errors and out_file: | 239 if errors and out_file: |
238 if os.path.exists(out_file): | 240 if os.path.exists(out_file): |
239 os.remove(out_file) | 241 os.remove(out_file) |
240 if os.path.exists(self._MAP_FILE_FORMAT % out_file): | 242 if os.path.exists(self._MAP_FILE_FORMAT % out_file): |
241 os.remove(self._MAP_FILE_FORMAT % out_file) | 243 os.remove(self._MAP_FILE_FORMAT % out_file) |
242 | 244 |
243 return errors, stderr | 245 return errors, stderr |
244 | 246 |
245 def check(self, source_file, out_file=None, depends=None, externs=None, | 247 def check(self, source_file, out_file=None, depends=None, externs=None, |
246 closure_args=None): | 248 runner_args=None, closure_args=None): |
247 """Closure compiler |source_file| while checking for errors. | 249 """Closure compiler |source_file| while checking for errors. |
248 | 250 |
249 Args: | 251 Args: |
250 source_file: A file to check. | 252 source_file: A file to check. |
251 out_file: A file where the compiled output is written to. | 253 out_file: A file where the compiled output is written to. |
252 depends: Files that |source_file| requires to run (e.g. earlier <script>). | 254 depends: Files that |source_file| requires to run (e.g. earlier <script>). |
253 externs: @extern files that inform the compiler about custom globals. | 255 externs: @extern files that inform the compiler about custom globals. |
| 256 runner_args: Arguments passed to runner.jar. |
254 closure_args: Arguments passed directly to the Closure compiler. | 257 closure_args: Arguments passed directly to the Closure compiler. |
255 | 258 |
256 Returns: | 259 Returns: |
257 (found_errors, stderr) A boolean indicating whether errors were found and | 260 (found_errors, stderr) A boolean indicating whether errors were found and |
258 the raw Closure compiler stderr (as a string). | 261 the raw Closure compiler stderr (as a string). |
259 """ | 262 """ |
260 self._log_debug("FILE: %s" % source_file) | 263 self._log_debug("FILE: %s" % source_file) |
261 | 264 |
262 if source_file.endswith("_externs.js"): | 265 if source_file.endswith("_externs.js"): |
263 self._log_debug("Skipping externs: %s" % source_file) | 266 self._log_debug("Skipping externs: %s" % source_file) |
264 return | 267 return |
265 | 268 |
266 self._file_arg = source_file | 269 self._file_arg = source_file |
267 | 270 |
268 cwd, tmp_dir = os.getcwd(), tempfile.gettempdir() | 271 cwd, tmp_dir = os.getcwd(), tempfile.gettempdir() |
269 rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f) | 272 rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f) |
270 | 273 |
271 depends = depends or [] | 274 depends = depends or [] |
272 includes = [rel_path(f) for f in depends + [source_file]] | 275 includes = [rel_path(f) for f in depends + [source_file]] |
273 contents = ['<include src="%s">' % i for i in includes] | 276 contents = ['<include src="%s">' % i for i in includes] |
274 meta_file = self._create_temp_file("\n".join(contents)) | 277 meta_file = self._create_temp_file("\n".join(contents)) |
275 self._log_debug("Meta file: %s" % meta_file) | 278 self._log_debug("Meta file: %s" % meta_file) |
276 | 279 |
277 self._processor = processor.Processor(meta_file) | 280 self._processor = processor.Processor(meta_file) |
278 self._expanded_file = self._create_temp_file(self._processor.contents) | 281 self._expanded_file = self._create_temp_file(self._processor.contents) |
279 self._log_debug("Expanded file: %s" % self._expanded_file) | 282 self._log_debug("Expanded file: %s" % self._expanded_file) |
280 | 283 |
281 errors, stderr = self._run_js_check([self._expanded_file], | 284 errors, stderr = self._run_js_check([self._expanded_file], |
282 out_file=out_file, externs=externs, | 285 out_file=out_file, externs=externs, |
| 286 runner_args=runner_args, |
283 closure_args=closure_args) | 287 closure_args=closure_args) |
284 filtered_errors = self._filter_errors(errors) | 288 filtered_errors = self._filter_errors(errors) |
285 cleaned_errors = map(self._clean_up_error, filtered_errors) | 289 cleaned_errors = map(self._clean_up_error, filtered_errors) |
286 output = self._format_errors(cleaned_errors) | 290 output = self._format_errors(cleaned_errors) |
287 | 291 |
288 if cleaned_errors: | 292 if cleaned_errors: |
289 prefix = "\n" if output else "" | 293 prefix = "\n" if output else "" |
290 self._log_error("Error in: %s%s%s" % (source_file, prefix, output)) | 294 self._log_error("Error in: %s%s%s" % (source_file, prefix, output)) |
291 elif output: | 295 elif output: |
292 self._log_debug("Output: %s" % output) | 296 self._log_debug("Output: %s" % output) |
293 | 297 |
294 self._nuke_temp_files() | 298 self._nuke_temp_files() |
295 return bool(cleaned_errors), stderr | 299 return bool(cleaned_errors), stderr |
296 | 300 |
297 def check_multiple(self, sources, out_file=None, externs=None, | 301 def check_multiple(self, sources, out_file=None, externs=None, |
298 closure_args=None): | 302 runner_args=None, closure_args=None): |
299 """Closure compile a set of files and check for errors. | 303 """Closure compile a set of files and check for errors. |
300 | 304 |
301 Args: | 305 Args: |
302 sources: An array of files to check. | 306 sources: An array of files to check. |
303 out_file: A file where the compiled output is written to. | 307 out_file: A file where the compiled output is written to. |
304 externs: @extern files that inform the compiler about custom globals. | 308 externs: @extern files that inform the compiler about custom globals. |
| 309 runner_args: Arguments passed to runner.jar. |
305 closure_args: Arguments passed directly to the Closure compiler. | 310 closure_args: Arguments passed directly to the Closure compiler. |
306 | 311 |
307 Returns: | 312 Returns: |
308 (found_errors, stderr) A boolean indicating whether errors were found and | 313 (found_errors, stderr) A boolean indicating whether errors were found and |
309 the raw Closure Compiler stderr (as a string). | 314 the raw Closure Compiler stderr (as a string). |
310 """ | 315 """ |
311 errors, stderr = self._run_js_check(sources, out_file=out_file, | 316 errors, stderr = self._run_js_check(sources, out_file=out_file, |
312 externs=externs, | 317 externs=externs, |
| 318 runner_args=runner_args, |
313 closure_args=closure_args) | 319 closure_args=closure_args) |
314 self._nuke_temp_files() | 320 self._nuke_temp_files() |
315 return bool(errors), stderr | 321 return bool(errors), stderr |
316 | 322 |
317 | 323 |
318 if __name__ == "__main__": | 324 if __name__ == "__main__": |
319 parser = argparse.ArgumentParser( | 325 parser = argparse.ArgumentParser( |
320 description="Typecheck JavaScript using Closure compiler") | 326 description="Typecheck JavaScript using Closure compiler") |
321 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, | 327 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, |
322 help="Path to a source file to typecheck") | 328 help="Path to a source file to typecheck") |
323 single_file_group = parser.add_mutually_exclusive_group() | 329 single_file_group = parser.add_mutually_exclusive_group() |
324 single_file_group.add_argument("--single-file", dest="single_file", | 330 single_file_group.add_argument("--single_file", dest="single_file", |
325 action="store_true", | 331 action="store_true", |
326 help="Process each source file individually") | 332 help="Process each source file individually") |
327 # TODO(twellington): remove --no-single-file and use len(opts.sources). | 333 # TODO(twellington): remove --no_single_file and use len(opts.sources). |
328 single_file_group.add_argument("--no-single-file", dest="single_file", | 334 single_file_group.add_argument("--no_single_file", dest="single_file", |
329 action="store_false", | 335 action="store_false", |
330 help="Process all source files as a group") | 336 help="Process all source files as a group") |
331 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) | 337 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) |
332 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) | 338 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) |
333 parser.add_argument("-o", "--out-file", dest="out_file", | 339 parser.add_argument("-o", "--out_file", |
334 help="A file where the compiled output is written to") | 340 help="A file where the compiled output is written to") |
335 parser.add_argument("-c", "--closure-args", dest="closure_args", | 341 parser.add_argument("-r", "--runner_args", nargs=argparse.ZERO_OR_MORE, |
336 nargs=argparse.ZERO_OR_MORE, | 342 help="Arguments passed to runner.jar") |
| 343 parser.add_argument("-c", "--closure_args", nargs=argparse.ZERO_OR_MORE, |
337 help="Arguments passed directly to the Closure compiler") | 344 help="Arguments passed directly to the Closure compiler") |
338 parser.add_argument("-v", "--verbose", action="store_true", | 345 parser.add_argument("-v", "--verbose", action="store_true", |
339 help="Show more information as this script runs") | 346 help="Show more information as this script runs") |
340 | 347 |
341 parser.set_defaults(single_file=True, strict=False) | 348 parser.set_defaults(single_file=True, strict=False) |
342 opts = parser.parse_args() | 349 opts = parser.parse_args() |
343 | 350 |
344 depends = opts.depends or [] | 351 depends = opts.depends or [] |
345 # TODO(devlin): should we run normpath() on this first and/or do this for | 352 # TODO(devlin): should we run normpath() on this first and/or do this for |
346 # depends as well? | 353 # depends as well? |
347 externs = set(opts.externs or []) | 354 externs = set(opts.externs or []) |
348 sources = set(opts.sources) | 355 sources = set(opts.sources) |
349 | 356 |
350 externs.add(os.path.join(_CURRENT_DIR, "externs", "polymer-1.0.js")) | 357 externs.add(os.path.join(_CURRENT_DIR, "externs", "polymer-1.0.js")) |
351 | 358 |
352 checker = Checker(verbose=opts.verbose, strict=opts.strict) | 359 checker = Checker(verbose=opts.verbose, strict=opts.strict) |
353 if opts.single_file: | 360 if opts.single_file: |
354 for source in sources: | 361 for source in sources: |
355 # Normalize source to the current directory. | 362 # Normalize source to the current directory. |
356 source = os.path.normpath(os.path.join(os.getcwd(), source)) | 363 source = os.path.normpath(os.path.join(os.getcwd(), source)) |
357 depends, externs = build.inputs.resolve_recursive_dependencies( | 364 depends, externs = build.inputs.resolve_recursive_dependencies( |
358 source, depends, externs) | 365 source, depends, externs) |
359 | 366 |
360 found_errors, _ = checker.check(source, out_file=opts.out_file, | 367 found_errors, _ = checker.check(source, out_file=opts.out_file, |
361 depends=depends, externs=externs, | 368 depends=depends, externs=externs, |
| 369 runner_args=opts.runner_args, |
362 closure_args=opts.closure_args) | 370 closure_args=opts.closure_args) |
363 if found_errors: | 371 if found_errors: |
364 sys.exit(1) | 372 sys.exit(1) |
365 else: | 373 else: |
366 found_errors, stderr = checker.check_multiple( | 374 found_errors, stderr = checker.check_multiple( |
367 sources, | 375 sources, |
368 out_file=opts.out_file, | 376 out_file=opts.out_file, |
369 externs=externs, | 377 externs=externs, |
| 378 runner_args=opts.runner_args, |
370 closure_args=opts.closure_args) | 379 closure_args=opts.closure_args) |
371 if found_errors: | 380 if found_errors: |
372 print stderr | 381 print stderr |
373 sys.exit(1) | 382 sys.exit(1) |
OLD | NEW |