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 |