| 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 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 61 current_dir = os.path.join(os.path.dirname(__file__)) | 61 current_dir = os.path.join(os.path.dirname(__file__)) |
| 62 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") | 62 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") |
| 63 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") | 63 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") |
| 64 self._temp_files = [] | 64 self._temp_files = [] |
| 65 self._verbose = verbose | 65 self._verbose = verbose |
| 66 | 66 |
| 67 def _clean_up(self): | 67 def _clean_up(self): |
| 68 if not self._temp_files: | 68 if not self._temp_files: |
| 69 return | 69 return |
| 70 | 70 |
| 71 self._debug("Deleting temporary files: " + ", ".join(self._temp_files)) | 71 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) |
| 72 for f in self._temp_files: | 72 for f in self._temp_files: |
| 73 os.remove(f) | 73 os.remove(f) |
| 74 self._temp_files = [] | 74 self._temp_files = [] |
| 75 | 75 |
| 76 def _debug(self, msg, error=False): | 76 def _debug(self, msg, error=False): |
| 77 if self._verbose: | 77 if self._verbose: |
| 78 print "(INFO) " + msg | 78 print "(INFO) %s" % msg |
| 79 | 79 |
| 80 def _error(self, msg): | 80 def _error(self, msg): |
| 81 print >> sys.stderr, "(ERROR) " + msg | 81 print >> sys.stderr, "(ERROR) %s" % msg |
| 82 self._clean_up() | 82 self._clean_up() |
| 83 | 83 |
| 84 def _run_command(self, cmd): | 84 def _run_command(self, cmd): |
| 85 """Runs a shell command. | 85 """Runs a shell command. |
| 86 | 86 |
| 87 Args: | 87 Args: |
| 88 cmd: A list of tokens to be joined into a shell command. | 88 cmd: A list of tokens to be joined into a shell command. |
| 89 | 89 |
| 90 Return: | 90 Return: |
| 91 True if the exit code was 0, else False. | 91 True if the exit code was 0, else False. |
| 92 """ | 92 """ |
| 93 cmd_str = " ".join(cmd) | 93 cmd_str = " ".join(cmd) |
| 94 self._debug("Running command: " + cmd_str) | 94 self._debug("Running command: %s" % cmd_str) |
| 95 | 95 |
| 96 devnull = open(os.devnull, "w") | 96 devnull = open(os.devnull, "w") |
| 97 return subprocess.Popen( | 97 return subprocess.Popen( |
| 98 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) | 98 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) |
| 99 | 99 |
| 100 def _check_java_path(self): | 100 def _check_java_path(self): |
| 101 """Checks that `java` is on the system path.""" | 101 """Checks that `java` is on the system path.""" |
| 102 if not self._found_java: | 102 if not self._found_java: |
| 103 proc = self._run_command(["which", "java"]) | 103 proc = self._run_command(["which", "java"]) |
| 104 proc.communicate() | 104 proc.communicate() |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 139 # Ignore "Variable x first declared in /same/file". | 139 # Ignore "Variable x first declared in /same/file". |
| 140 return "" | 140 return "" |
| 141 | 141 |
| 142 expanded_file = self._expanded_file | 142 expanded_file = self._expanded_file |
| 143 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error) | 143 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error) |
| 144 return fixed.replace(expanded_file, os.path.abspath(self._file_arg)) | 144 return fixed.replace(expanded_file, os.path.abspath(self._file_arg)) |
| 145 | 145 |
| 146 def _format_errors(self, errors): | 146 def _format_errors(self, errors): |
| 147 """Formats Closure compiler errors to easily spot compiler output.""" | 147 """Formats Closure compiler errors to easily spot compiler output.""" |
| 148 errors = filter(None, errors) | 148 errors = filter(None, errors) |
| 149 contents = ("\n" + "## ").join("\n\n".join(errors).splitlines()) | 149 contents = "\n## ".join("\n\n".join(errors).splitlines()) |
| 150 return "## " + contents if contents else "" | 150 return "## %s" % contents if contents else "" |
| 151 | 151 |
| 152 def _create_temp_file(self, contents): | 152 def _create_temp_file(self, contents): |
| 153 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: | 153 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: |
| 154 self._temp_files.append(tmp_file.name) | 154 self._temp_files.append(tmp_file.name) |
| 155 tmp_file.write(contents) | 155 tmp_file.write(contents) |
| 156 return tmp_file.name | 156 return tmp_file.name |
| 157 | 157 |
| 158 def check(self, source_file, depends=None, externs=None): | 158 def check(self, source_file, depends=None, externs=None): |
| 159 """Closure compile a file and check for errors. | 159 """Closure compile a file and check for errors. |
| 160 | 160 |
| 161 Args: | 161 Args: |
| 162 source_file: A file to check. | 162 source_file: A file to check. |
| 163 depends: Other files that would be included with a <script> earlier in | 163 depends: Other files that would be included with a <script> earlier in |
| 164 the page. | 164 the page. |
| 165 externs: @extern files that inform the compiler about custom globals. | 165 externs: @extern files that inform the compiler about custom globals. |
| 166 | 166 |
| 167 Returns: | 167 Returns: |
| 168 (exitcode, output) The exit code of the Closure compiler (as a number) | 168 (exitcode, output) The exit code of the Closure compiler (as a number) |
| 169 and its output (as a string). | 169 and its output (as a string). |
| 170 """ | 170 """ |
| 171 depends = depends or [] | 171 depends = depends or [] |
| 172 externs = externs or [] | 172 externs = externs or [] |
| 173 | 173 |
| 174 if not self._check_java_path(): | 174 if not self._check_java_path(): |
| 175 return 1, "" | 175 return 1, "" |
| 176 | 176 |
| 177 self._debug("FILE: " + source_file) | 177 self._debug("FILE: %s" % source_file) |
| 178 | 178 |
| 179 if source_file.endswith("_externs.js"): | 179 if source_file.endswith("_externs.js"): |
| 180 self._debug("Skipping externs: " + source_file) | 180 self._debug("Skipping externs: %s" % source_file) |
| 181 return | 181 return |
| 182 | 182 |
| 183 self._file_arg = source_file | 183 self._file_arg = source_file |
| 184 | 184 |
| 185 tmp_dir = tempfile.gettempdir() | 185 tmp_dir = tempfile.gettempdir() |
| 186 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) | 186 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) |
| 187 | 187 |
| 188 includes = [rel_path(f) for f in depends + [source_file]] | 188 includes = [rel_path(f) for f in depends + [source_file]] |
| 189 contents = ['<include src="%s">' % i for i in includes] | 189 contents = ['<include src="%s">' % i for i in includes] |
| 190 meta_file = self._create_temp_file("\n".join(contents)) | 190 meta_file = self._create_temp_file("\n".join(contents)) |
| 191 self._debug("Meta file: " + meta_file) | 191 self._debug("Meta file: %s" % meta_file) |
| 192 | 192 |
| 193 self._processor = processor.Processor(meta_file) | 193 self._processor = processor.Processor(meta_file) |
| 194 self._expanded_file = self._create_temp_file(self._processor.contents) | 194 self._expanded_file = self._create_temp_file(self._processor.contents) |
| 195 self._debug("Expanded file: " + self._expanded_file) | 195 self._debug("Expanded file: %s" % self._expanded_file) |
| 196 | 196 |
| 197 args = ["--js=" + self._expanded_file] + ["--externs=" + e for e in externs] | 197 args = ["--js=%s" % self._expanded_file] |
| 198 args_file_content = " " + " ".join(self._COMMON_CLOSURE_ARGS + args) | 198 args += ["--externs=%s" % e for e in externs] |
| 199 self._debug("Args: " + args_file_content.strip()) | 199 args_file_content = " %s" % " ".join(self._COMMON_CLOSURE_ARGS + args) |
| 200 self._debug("Args: %s" % args_file_content.strip()) |
| 200 | 201 |
| 201 args_file = self._create_temp_file(args_file_content) | 202 args_file = self._create_temp_file(args_file_content) |
| 202 self._debug("Args file: " + args_file) | 203 self._debug("Args file: %s" % args_file) |
| 203 | 204 |
| 204 runner_args = ["--compiler-args-file=" + args_file] | 205 runner_args = ["--compiler-args-file=%s" % args_file] |
| 205 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) | 206 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) |
| 206 (_, stderr) = runner_cmd.communicate() | 207 (_, stderr) = runner_cmd.communicate() |
| 207 | 208 |
| 208 errors = stderr.strip().split("\n\n") | 209 errors = stderr.strip().split("\n\n") |
| 209 self._debug("Summary: " + errors.pop()) | 210 self._debug("Summary: %s" % errors.pop()) |
| 210 | 211 |
| 211 output = self._format_errors(map(self._fix_up_error, errors)) | 212 output = self._format_errors(map(self._fix_up_error, errors)) |
| 212 if runner_cmd.returncode: | 213 if runner_cmd.returncode: |
| 213 self._error("Error in: " + source_file + ("\n" + output if output else "")
) | 214 self._error("Error in: %s%s" % (source_file, "\n" + output if output else
"")) |
| 214 elif output: | 215 elif output: |
| 215 self._debug("Output: " + output) | 216 self._debug("Output: %s" % output) |
| 216 | 217 |
| 217 self._clean_up() | 218 self._clean_up() |
| 218 | 219 |
| 219 return runner_cmd.returncode, output | 220 return runner_cmd.returncode, output |
| 220 | 221 |
| 221 | 222 |
| 222 if __name__ == "__main__": | 223 if __name__ == "__main__": |
| 223 parser = argparse.ArgumentParser( | 224 parser = argparse.ArgumentParser( |
| 224 description="Typecheck JavaScript using Closure compiler") | 225 description="Typecheck JavaScript using Closure compiler") |
| 225 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, | 226 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, |
| 226 help="Path to a source file to typecheck") | 227 help="Path to a source file to typecheck") |
| 227 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) | 228 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) |
| 228 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) | 229 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) |
| 229 parser.add_argument("-o", "--out_file", help="A place to output results") | 230 parser.add_argument("-o", "--out_file", help="A place to output results") |
| 230 parser.add_argument("-v", "--verbose", action="store_true", | 231 parser.add_argument("-v", "--verbose", action="store_true", |
| 231 help="Show more information as this script runs") | 232 help="Show more information as this script runs") |
| 232 opts = parser.parse_args() | 233 opts = parser.parse_args() |
| 233 | 234 |
| 234 checker = Checker(verbose=opts.verbose) | 235 checker = Checker(verbose=opts.verbose) |
| 235 for source in opts.sources: | 236 for source in opts.sources: |
| 236 if not checker.check(source, depends=opts.depends, externs=opts.externs): | 237 if not checker.check(source, depends=opts.depends, externs=opts.externs): |
| 237 sys.exit(1) | 238 sys.exit(1) |
| 238 | 239 |
| 239 if opts.out_file: | 240 if opts.out_file: |
| 240 out_dir = os.path.dirname(opts.out_file) | 241 out_dir = os.path.dirname(opts.out_file) |
| 241 if not os.path.exists(out_dir): | 242 if not os.path.exists(out_dir): |
| 242 os.makedirs(out_dir) | 243 os.makedirs(out_dir) |
| 243 # TODO(dbeam): write compiled file to |opts.out_file|. | 244 # TODO(dbeam): write compiled file to |opts.out_file|. |
| 244 open(opts.out_file, "w").write("") | 245 open(opts.out_file, "w").write("") |
| OLD | NEW |