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 26 matching lines...) Expand all Loading... |
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", | 45 "--language_in=ECMASCRIPT5_STRICT", |
46 "--summary_detail_level=3", | 46 "--summary_detail_level=3", |
47 "--compilation_level=WHITESPACE_ONLY", | |
48 "--source_map_format=V3", | |
49 ] | 47 ] |
50 | 48 |
51 # These are the extra flags used when compiling in 'strict' mode. | 49 # These are the extra flags used when compiling in 'strict' mode. |
52 # Flags that are normally disabled are turned on for strict mode. | 50 # Flags that are normally disabled are turned on for strict mode. |
53 _STRICT_CLOSURE_ARGS = [ | 51 _STRICT_CLOSURE_ARGS = [ |
54 "--jscomp_error=reportUnknownTypes", | 52 "--jscomp_error=reportUnknownTypes", |
55 "--jscomp_error=duplicate", | 53 "--jscomp_error=duplicate", |
56 "--jscomp_error=misplacedTypeAnnotation", | 54 "--jscomp_error=misplacedTypeAnnotation", |
57 ] | 55 ] |
58 | 56 |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
172 errors = filter(None, errors) | 170 errors = filter(None, errors) |
173 contents = "\n## ".join("\n\n".join(errors).splitlines()) | 171 contents = "\n## ".join("\n\n".join(errors).splitlines()) |
174 return "## %s" % contents if contents else "" | 172 return "## %s" % contents if contents else "" |
175 | 173 |
176 def _create_temp_file(self, contents): | 174 def _create_temp_file(self, contents): |
177 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: | 175 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: |
178 self._temp_files.append(tmp_file.name) | 176 self._temp_files.append(tmp_file.name) |
179 tmp_file.write(contents) | 177 tmp_file.write(contents) |
180 return tmp_file.name | 178 return tmp_file.name |
181 | 179 |
182 def _run_js_check(self, sources, out_file=None, externs=None): | 180 def run_js_check(self, sources, externs=None): |
183 if not self._check_java_path(): | 181 if not self._check_java_path(): |
184 return 1, "" | 182 return 1, "" |
185 | 183 |
186 args = ["--js=%s" % s for s in sources] | 184 args = ["--js=%s" % s for s in sources] |
187 | |
188 if out_file: | |
189 args += ["--js_output_file=%s" % out_file] | |
190 args += ["--create_source_map=%s.map" % out_file] | |
191 | |
192 if externs: | 185 if externs: |
193 args += ["--externs=%s" % e for e in externs] | 186 args += ["--externs=%s" % e for e in externs] |
194 args_file_content = " %s" % " ".join(self._common_args() + args) | 187 args_file_content = " %s" % " ".join(self._common_args() + args) |
195 self._debug("Args: %s" % args_file_content.strip()) | 188 self._debug("Args: %s" % args_file_content.strip()) |
196 | 189 |
197 args_file = self._create_temp_file(args_file_content) | 190 args_file = self._create_temp_file(args_file_content) |
198 self._debug("Args file: %s" % args_file) | 191 self._debug("Args file: %s" % args_file) |
199 | 192 |
200 runner_args = ["--compiler-args-file=%s" % args_file] | 193 runner_args = ["--compiler-args-file=%s" % args_file] |
201 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) | 194 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) |
202 _, stderr = runner_cmd.communicate() | 195 _, stderr = runner_cmd.communicate() |
203 | 196 |
204 errors = stderr.strip().split("\n\n") | 197 errors = stderr.strip().split("\n\n") |
205 self._debug("Summary: %s" % errors.pop()) | 198 self._debug("Summary: %s" % errors.pop()) |
206 | 199 |
207 self._clean_up() | 200 self._clean_up() |
208 | 201 |
209 return errors, stderr | 202 return errors, stderr |
210 | 203 |
211 def check(self, source_file, out_file=None, depends=None, externs=None): | 204 def check(self, source_file, depends=None, externs=None): |
212 """Closure compile a file and check for errors. | 205 """Closure compile a file and check for errors. |
213 | 206 |
214 Args: | 207 Args: |
215 source_file: A file to check. | 208 source_file: A file to check. |
216 out_file: A file where the compiled output is written to. | |
217 depends: Other files that would be included with a <script> earlier in | 209 depends: Other files that would be included with a <script> earlier in |
218 the page. | 210 the page. |
219 externs: @extern files that inform the compiler about custom globals. | 211 externs: @extern files that inform the compiler about custom globals. |
220 | 212 |
221 Returns: | 213 Returns: |
222 (has_errors, output) A boolean indicating if there were errors and the | 214 (has_errors, output) A boolean indicating if there were errors and the |
223 Closure compiler output (as a string). | 215 Closure compiler output (as a string). |
224 """ | 216 """ |
225 depends = depends or [] | 217 depends = depends or [] |
226 externs = externs or set() | 218 externs = externs or set() |
(...skipping 14 matching lines...) Expand all Loading... |
241 | 233 |
242 includes = [rel_path(f) for f in depends + [source_file]] | 234 includes = [rel_path(f) for f in depends + [source_file]] |
243 contents = ['<include src="%s">' % i for i in includes] | 235 contents = ['<include src="%s">' % i for i in includes] |
244 meta_file = self._create_temp_file("\n".join(contents)) | 236 meta_file = self._create_temp_file("\n".join(contents)) |
245 self._debug("Meta file: %s" % meta_file) | 237 self._debug("Meta file: %s" % meta_file) |
246 | 238 |
247 self._processor = processor.Processor(meta_file) | 239 self._processor = processor.Processor(meta_file) |
248 self._expanded_file = self._create_temp_file(self._processor.contents) | 240 self._expanded_file = self._create_temp_file(self._processor.contents) |
249 self._debug("Expanded file: %s" % self._expanded_file) | 241 self._debug("Expanded file: %s" % self._expanded_file) |
250 | 242 |
251 errors, stderr = self._run_js_check([self._expanded_file], | 243 errors, stderr = self.run_js_check([self._expanded_file], externs) |
252 out_file=out_file, externs=externs) | |
253 | 244 |
254 # Filter out false-positive promise chain errors. | 245 # Filter out false-positive promise chain errors. |
255 # See https://github.com/google/closure-compiler/issues/715 for details. | 246 # See https://github.com/google/closure-compiler/issues/715 for details. |
256 errors = self._error_filter.filter(errors); | 247 errors = self._error_filter.filter(errors); |
257 | 248 |
258 output = self._format_errors(map(self._fix_up_error, errors)) | 249 output = self._format_errors(map(self._fix_up_error, errors)) |
259 if errors: | 250 if errors: |
260 prefix = "\n" if output else "" | 251 prefix = "\n" if output else "" |
261 self._error("Error in: %s%s%s" % (source_file, prefix, output)) | 252 self._error("Error in: %s%s%s" % (source_file, prefix, output)) |
262 elif output: | 253 elif output: |
(...skipping 22 matching lines...) Expand all Loading... |
285 help="Path to a source file to typecheck") | 276 help="Path to a source file to typecheck") |
286 single_file_group = parser.add_mutually_exclusive_group() | 277 single_file_group = parser.add_mutually_exclusive_group() |
287 single_file_group.add_argument("--single-file", dest="single_file", | 278 single_file_group.add_argument("--single-file", dest="single_file", |
288 action="store_true", | 279 action="store_true", |
289 help="Process each source file individually") | 280 help="Process each source file individually") |
290 single_file_group.add_argument("--no-single-file", dest="single_file", | 281 single_file_group.add_argument("--no-single-file", dest="single_file", |
291 action="store_false", | 282 action="store_false", |
292 help="Process all source files as a group") | 283 help="Process all source files as a group") |
293 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) | 284 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) |
294 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) | 285 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) |
295 parser.add_argument("-o", "--out_file", | 286 parser.add_argument("-o", "--out_file", help="A place to output results") |
296 help="A file where the compiled output is written to") | |
297 parser.add_argument("-v", "--verbose", action="store_true", | 287 parser.add_argument("-v", "--verbose", action="store_true", |
298 help="Show more information as this script runs") | 288 help="Show more information as this script runs") |
299 parser.add_argument("--strict", action="store_true", | 289 parser.add_argument("--strict", action="store_true", |
300 help="Enable strict type checking") | 290 help="Enable strict type checking") |
301 parser.add_argument("--success-stamp", | 291 parser.add_argument("--success-stamp", |
302 help="Timestamp file to update upon success") | 292 help="Timestamp file to update upon success") |
303 | 293 |
304 parser.set_defaults(single_file=True, strict=False) | 294 parser.set_defaults(single_file=True, strict=False) |
305 opts = parser.parse_args() | 295 opts = parser.parse_args() |
306 | 296 |
307 depends = opts.depends or [] | 297 depends = opts.depends or [] |
308 externs = opts.externs or set() | 298 externs = opts.externs or set() |
309 | 299 |
310 if opts.out_file: | |
311 out_dir = os.path.dirname(opts.out_file) | |
312 if not os.path.exists(out_dir): | |
313 os.makedirs(out_dir) | |
314 | |
315 checker = Checker(verbose=opts.verbose, strict=opts.strict) | 300 checker = Checker(verbose=opts.verbose, strict=opts.strict) |
316 if opts.single_file: | 301 if opts.single_file: |
317 for source in opts.sources: | 302 for source in opts.sources: |
318 depends, externs = build.inputs.resolve_recursive_dependencies( | 303 depends, externs = build.inputs.resolve_recursive_dependencies( |
319 source, | 304 source, |
320 depends, | 305 depends, |
321 externs) | 306 externs) |
322 has_errors, _ = checker.check(source, out_file=opts.out_file, | 307 has_errors, _ = checker.check(source, depends=depends, externs=externs) |
323 depends=depends, externs=externs) | |
324 if has_errors: | 308 if has_errors: |
325 sys.exit(1) | 309 sys.exit(1) |
326 | 310 |
| 311 if opts.out_file: |
| 312 out_dir = os.path.dirname(opts.out_file) |
| 313 if not os.path.exists(out_dir): |
| 314 os.makedirs(out_dir) |
| 315 # TODO(dbeam): write compiled file to |opts.out_file|. |
| 316 open(opts.out_file, "w").write("") |
327 else: | 317 else: |
328 has_errors, errors = checker.check_multiple(opts.sources) | 318 has_errors, errors = checker.check_multiple(opts.sources) |
329 if has_errors: | 319 if has_errors: |
330 print errors | 320 print errors |
331 sys.exit(1) | 321 sys.exit(1) |
332 | 322 |
333 if opts.success_stamp: | 323 if opts.success_stamp: |
334 with open(opts.success_stamp, 'w'): | 324 with open(opts.success_stamp, 'w'): |
335 os.utime(opts.success_stamp, None) | 325 os.utime(opts.success_stamp, None) |
OLD | NEW |