Chromium Code Reviews| 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 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 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 35 "--jscomp_error=invalidCasts", | 35 "--jscomp_error=invalidCasts", |
| 36 "--jscomp_error=missingProperties", | 36 "--jscomp_error=missingProperties", |
| 37 "--jscomp_error=missingReturn", | 37 "--jscomp_error=missingReturn", |
| 38 "--jscomp_error=nonStandardJsDocs", | 38 "--jscomp_error=nonStandardJsDocs", |
| 39 "--jscomp_error=suspiciousCode", | 39 "--jscomp_error=suspiciousCode", |
| 40 "--jscomp_error=undefinedNames", | 40 "--jscomp_error=undefinedNames", |
| 41 "--jscomp_error=undefinedVars", | 41 "--jscomp_error=undefinedVars", |
| 42 "--jscomp_error=unknownDefines", | 42 "--jscomp_error=unknownDefines", |
| 43 "--jscomp_error=uselessCode", | 43 "--jscomp_error=uselessCode", |
| 44 "--jscomp_error=visibility", | 44 "--jscomp_error=visibility", |
| 45 "--language_in=ECMASCRIPT5_STRICT", | |
| 46 "--summary_detail_level=3", | |
| 47 ] | |
| 48 | |
| 49 # These are the extra flags used when compiling in 'strict' mode. | |
| 50 # Flags that are normally disabled are turned on for strict mode. | |
| 51 _STRICT_CLOSURE_ARGS = [ | |
| 52 "--jscomp_error=reportUnknownTypes", | |
| 53 "--jscomp_error=duplicate", | |
| 54 "--jscomp_error=misplacedTypeAnnotation", | |
| 55 ] | |
| 56 | |
| 57 _DISABLED_CLOSURE_ARGS = [ | |
| 45 # TODO(dbeam): happens when the same file is <include>d multiple times. | 58 # TODO(dbeam): happens when the same file is <include>d multiple times. |
| 46 "--jscomp_off=duplicate", | 59 "--jscomp_off=duplicate", |
| 47 # TODO(fukino): happens when cr.defineProperty() has a type annotation. | 60 # TODO(fukino): happens when cr.defineProperty() has a type annotation. |
| 48 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562. | 61 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562. |
| 49 "--jscomp_off=misplacedTypeAnnotation", | 62 "--jscomp_off=misplacedTypeAnnotation", |
| 50 "--language_in=ECMASCRIPT5_STRICT", | |
| 51 "--summary_detail_level=3", | |
| 52 ] | 63 ] |
| 53 | 64 |
| 54 _JAR_COMMAND = [ | 65 _JAR_COMMAND = [ |
| 55 "java", | 66 "java", |
| 56 "-jar", | 67 "-jar", |
| 57 "-Xms1024m", | 68 "-Xms1024m", |
| 58 "-client", | 69 "-client", |
| 59 "-XX:+TieredCompilation" | 70 "-XX:+TieredCompilation" |
| 60 ] | 71 ] |
| 61 | 72 |
| 62 _found_java = False | 73 _found_java = False |
| 63 | 74 |
| 64 def __init__(self, verbose=False): | 75 def __init__(self, verbose=False, strict=False): |
| 65 current_dir = os.path.join(os.path.dirname(__file__)) | 76 current_dir = os.path.join(os.path.dirname(__file__)) |
| 66 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") | |
|
garykac
2015/01/12 20:22:37
This is never used.
| |
| 67 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") | 77 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") |
| 68 self._temp_files = [] | 78 self._temp_files = [] |
| 69 self._verbose = verbose | 79 self._verbose = verbose |
| 80 self._strict = strict | |
| 70 self._error_filter = error_filter.PromiseErrorFilter() | 81 self._error_filter = error_filter.PromiseErrorFilter() |
| 71 | 82 |
| 72 def _clean_up(self): | 83 def _clean_up(self): |
| 73 if not self._temp_files: | 84 if not self._temp_files: |
| 74 return | 85 return |
| 75 | 86 |
| 76 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) | 87 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) |
| 77 for f in self._temp_files: | 88 for f in self._temp_files: |
| 78 os.remove(f) | 89 os.remove(f) |
| 79 self._temp_files = [] | 90 self._temp_files = [] |
| 80 | 91 |
| 81 def _debug(self, msg, error=False): | 92 def _debug(self, msg, error=False): |
| 82 if self._verbose: | 93 if self._verbose: |
| 83 print "(INFO) %s" % msg | 94 print "(INFO) %s" % msg |
| 84 | 95 |
| 85 def _error(self, msg): | 96 def _error(self, msg): |
| 86 print >> sys.stderr, "(ERROR) %s" % msg | 97 print >> sys.stderr, "(ERROR) %s" % msg |
| 87 self._clean_up() | 98 self._clean_up() |
| 88 | 99 |
| 100 def _common_args(self): | |
| 101 """Returns an array of the common closure compiler args.""" | |
| 102 if self._strict: | |
| 103 return self._COMMON_CLOSURE_ARGS + self._STRICT_CLOSURE_ARGS | |
| 104 return self._COMMON_CLOSURE_ARGS + self._DISABLED_CLOSURE_ARGS | |
| 105 | |
| 89 def _run_command(self, cmd): | 106 def _run_command(self, cmd): |
| 90 """Runs a shell command. | 107 """Runs a shell command. |
| 91 | 108 |
| 92 Args: | 109 Args: |
| 93 cmd: A list of tokens to be joined into a shell command. | 110 cmd: A list of tokens to be joined into a shell command. |
| 94 | 111 |
| 95 Return: | 112 Return: |
| 96 True if the exit code was 0, else False. | 113 True if the exit code was 0, else False. |
| 97 """ | 114 """ |
| 98 cmd_str = " ".join(cmd) | 115 cmd_str = " ".join(cmd) |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 163 def check(self, source_file, depends=None, externs=None): | 180 def check(self, source_file, depends=None, externs=None): |
| 164 """Closure compile a file and check for errors. | 181 """Closure compile a file and check for errors. |
| 165 | 182 |
| 166 Args: | 183 Args: |
| 167 source_file: A file to check. | 184 source_file: A file to check. |
| 168 depends: Other files that would be included with a <script> earlier in | 185 depends: Other files that would be included with a <script> earlier in |
| 169 the page. | 186 the page. |
| 170 externs: @extern files that inform the compiler about custom globals. | 187 externs: @extern files that inform the compiler about custom globals. |
| 171 | 188 |
| 172 Returns: | 189 Returns: |
| 173 (exitcode, output) The exit code of the Closure compiler (as a number) | 190 (has_errors, output) A boolean indicating if there were errors and the |
| 174 and its output (as a string). | 191 Closure compiler output (as a string). |
| 175 """ | 192 """ |
| 176 depends = depends or [] | 193 depends = depends or [] |
| 177 externs = externs or set() | 194 externs = externs or set() |
| 178 | 195 |
| 179 if not self._check_java_path(): | 196 if not self._check_java_path(): |
| 180 return 1, "" | 197 return 1, "" |
| 181 | 198 |
| 182 self._debug("FILE: %s" % source_file) | 199 self._debug("FILE: %s" % source_file) |
| 183 | 200 |
| 184 if source_file.endswith("_externs.js"): | 201 if source_file.endswith("_externs.js"): |
| 185 self._debug("Skipping externs: %s" % source_file) | 202 self._debug("Skipping externs: %s" % source_file) |
| 186 return | 203 return |
| 187 | 204 |
| 188 self._file_arg = source_file | 205 self._file_arg = source_file |
| 189 | 206 |
| 190 tmp_dir = tempfile.gettempdir() | 207 tmp_dir = tempfile.gettempdir() |
| 191 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) | 208 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) |
| 192 | 209 |
| 193 includes = [rel_path(f) for f in depends + [source_file]] | 210 includes = [rel_path(f) for f in depends + [source_file]] |
| 194 contents = ['<include src="%s">' % i for i in includes] | 211 contents = ['<include src="%s">' % i for i in includes] |
| 195 meta_file = self._create_temp_file("\n".join(contents)) | 212 meta_file = self._create_temp_file("\n".join(contents)) |
| 196 self._debug("Meta file: %s" % meta_file) | 213 self._debug("Meta file: %s" % meta_file) |
| 197 | 214 |
| 198 self._processor = processor.Processor(meta_file) | 215 self._processor = processor.Processor(meta_file) |
| 199 self._expanded_file = self._create_temp_file(self._processor.contents) | 216 self._expanded_file = self._create_temp_file(self._processor.contents) |
| 200 self._debug("Expanded file: %s" % self._expanded_file) | 217 self._debug("Expanded file: %s" % self._expanded_file) |
| 201 | 218 |
| 202 args = ["--js=%s" % self._expanded_file] | 219 args = ["--js=%s" % self._expanded_file] |
| 203 args += ["--externs=%s" % e for e in externs] | 220 args += ["--externs=%s" % e for e in externs] |
| 204 args_file_content = " %s" % " ".join(self._COMMON_CLOSURE_ARGS + args) | 221 args_file_content = " %s" % " ".join(self._common_args() + args) |
| 205 self._debug("Args: %s" % args_file_content.strip()) | 222 self._debug("Args: %s" % args_file_content.strip()) |
| 206 | 223 |
| 207 args_file = self._create_temp_file(args_file_content) | 224 args_file = self._create_temp_file(args_file_content) |
| 208 self._debug("Args file: %s" % args_file) | 225 self._debug("Args file: %s" % args_file) |
| 209 | 226 |
| 210 runner_args = ["--compiler-args-file=%s" % args_file] | 227 runner_args = ["--compiler-args-file=%s" % args_file] |
| 211 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) | 228 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) |
| 212 (_, stderr) = runner_cmd.communicate() | 229 (_, stderr) = runner_cmd.communicate() |
| 213 | 230 |
| 214 errors = stderr.strip().split("\n\n") | 231 errors = stderr.strip().split("\n\n") |
| 215 | 232 |
| 216 # Filter out false-positive promise chain errors. | 233 # Filter out false-positive promise chain errors. |
| 217 # See https://github.com/google/closure-compiler/issues/715 for details. | 234 # See https://github.com/google/closure-compiler/issues/715 for details. |
| 218 errors = self._error_filter.filter(errors); | 235 errors = self._error_filter.filter(errors); |
| 219 | 236 |
| 220 self._debug("Summary: %s" % errors.pop()) | 237 self._debug("Summary: %s" % errors.pop()) |
| 221 | 238 |
| 222 output = self._format_errors(map(self._fix_up_error, errors)) | 239 output = self._format_errors(map(self._fix_up_error, errors)) |
| 223 if errors: | 240 if errors: |
| 224 prefix = "\n" if output else "" | 241 prefix = "\n" if output else "" |
| 225 self._error("Error in: %s%s%s" % (source_file, prefix, output)) | 242 self._error("Error in: %s%s%s" % (source_file, prefix, output)) |
| 226 elif output: | 243 elif output: |
| 227 self._debug("Output: %s" % output) | 244 self._debug("Output: %s" % output) |
| 228 | 245 |
| 229 self._clean_up() | 246 self._clean_up() |
| 230 | 247 |
| 231 return bool(errors), output | 248 return bool(errors), output |
| 232 | 249 |
| 250 def check_multiple(self, sources): | |
| 251 """Closure compile a set of files and check for errors. | |
| 252 | |
| 253 Args: | |
| 254 sources: An array of files to check. | |
| 255 | |
| 256 Returns: | |
| 257 (has_errors, output) A boolean indicating if there were errors and the | |
| 258 Closure compiler output (as a string). | |
| 259 """ | |
| 260 | |
| 261 if not self._check_java_path(): | |
| 262 return 1, "" | |
| 263 | |
| 264 args = ["--js=%s" % s for s in sources] | |
| 265 args_file_content = " %s" % " ".join(self._common_args() + args) | |
| 266 self._debug("Args: %s" % args_file_content.strip()) | |
| 267 | |
| 268 args_file = self._create_temp_file(args_file_content) | |
| 269 self._debug("Args file: %s" % args_file) | |
| 270 | |
| 271 runner_args = ["--compiler-args-file=%s" % args_file] | |
| 272 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) | |
| 273 (_, stderr) = runner_cmd.communicate() | |
| 274 | |
| 275 errors = stderr.strip().split("\n\n") | |
| 276 self._debug("Summary: %s" % errors.pop()) | |
| 277 | |
| 278 return bool(errors), stderr | |
| 233 | 279 |
| 234 if __name__ == "__main__": | 280 if __name__ == "__main__": |
| 235 parser = argparse.ArgumentParser( | 281 parser = argparse.ArgumentParser( |
| 236 description="Typecheck JavaScript using Closure compiler") | 282 description="Typecheck JavaScript using Closure compiler") |
| 237 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, | 283 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, |
| 238 help="Path to a source file to typecheck") | 284 help="Path to a source file to typecheck") |
| 285 single_file_group = parser.add_mutually_exclusive_group() | |
| 286 single_file_group.add_argument("--single-file", dest="single_file", | |
| 287 action="store_true", | |
| 288 help="Process each source file individually") | |
| 289 single_file_group.add_argument("--no-single-file", dest="single_file", | |
| 290 action="store_false", | |
| 291 help="Process all source files as a group") | |
| 239 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) | 292 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) |
| 240 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) | 293 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) |
| 241 parser.add_argument("-o", "--out_file", help="A place to output results") | 294 parser.add_argument("-o", "--out_file", help="A place to output results") |
| 242 parser.add_argument("-v", "--verbose", action="store_true", | 295 parser.add_argument("-v", "--verbose", action="store_true", |
| 243 help="Show more information as this script runs") | 296 help="Show more information as this script runs") |
| 297 parser.add_argument("--strict", action="store_true", | |
| 298 help="Enable strict type checking") | |
| 299 parser.add_argument("--success-stamp", | |
| 300 help="Timestamp file to update upon success") | |
| 301 | |
| 302 parser.set_defaults(single_file=True, strict=False) | |
| 244 opts = parser.parse_args() | 303 opts = parser.parse_args() |
| 245 | 304 |
| 246 checker = Checker(verbose=opts.verbose) | 305 depends = opts.depends or [] |
| 247 for source in opts.sources: | 306 externs = opts.externs or set() |
| 248 depends, externs = build.inputs.resolve_recursive_dependencies( | 307 |
| 249 source, | 308 checker = Checker(verbose=opts.verbose, strict=opts.strict) |
| 250 opts.depends, | 309 if opts.single_file: |
| 251 opts.externs) | 310 for source in opts.sources: |
| 252 exit, _ = checker.check(source, depends=depends, externs=externs) | 311 depends, externs = build.inputs.resolve_recursive_dependencies( |
| 253 if exit != 0: | 312 source, |
| 313 depends, | |
| 314 externs) | |
| 315 has_errors, _ = checker.check(source, depends=depends, externs=externs) | |
| 316 if has_errors: | |
| 317 sys.exit(exit) | |
| 318 | |
| 319 if opts.out_file: | |
| 320 out_dir = os.path.dirname(opts.out_file) | |
| 321 if not os.path.exists(out_dir): | |
| 322 os.makedirs(out_dir) | |
| 323 # TODO(dbeam): write compiled file to |opts.out_file|. | |
| 324 open(opts.out_file, "w").write("") | |
| 325 else: | |
| 326 has_errors, _ = checker.check_multiple(opts.sources) | |
| 327 if has_errors: | |
| 328 print _ | |
| 254 sys.exit(exit) | 329 sys.exit(exit) |
| 255 | 330 |
| 256 if opts.out_file: | 331 if opts.success_stamp: |
| 257 out_dir = os.path.dirname(opts.out_file) | 332 with open(opts.success_stamp, 'w'): |
| 258 if not os.path.exists(out_dir): | 333 os.utime(opts.success_stamp, None) |
| 259 os.makedirs(out_dir) | |
| 260 # TODO(dbeam): write compiled file to |opts.out_file|. | |
| 261 open(opts.out_file, "w").write("") | |
| OLD | NEW |