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