ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / third_party / closure_compiler / checker.py
blob2eb9e68a21f606fc41e3e6ba4859649483138d13
1 #!/usr/bin/python
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
4 # found in the LICENSE file.
6 """Runs Closure compiler on a JavaScript file to check for errors."""
8 import argparse
9 import os
10 import re
11 import subprocess
12 import sys
13 import tempfile
15 import build.inputs
16 import processor
17 import error_filter
20 class Checker(object):
21 """Runs the Closure compiler on a given source file and returns the
22 success/errors."""
24 _COMMON_CLOSURE_ARGS = [
25 "--accept_const_keyword",
26 "--jscomp_error=accessControls",
27 "--jscomp_error=ambiguousFunctionDecl",
28 "--jscomp_error=checkStructDictInheritance",
29 "--jscomp_error=checkTypes",
30 "--jscomp_error=checkVars",
31 "--jscomp_error=constantProperty",
32 "--jscomp_error=deprecated",
33 "--jscomp_error=externsValidation",
34 "--jscomp_error=globalThis",
35 "--jscomp_error=invalidCasts",
36 "--jscomp_error=missingProperties",
37 "--jscomp_error=missingReturn",
38 "--jscomp_error=nonStandardJsDocs",
39 "--jscomp_error=suspiciousCode",
40 "--jscomp_error=undefinedNames",
41 "--jscomp_error=undefinedVars",
42 "--jscomp_error=unknownDefines",
43 "--jscomp_error=uselessCode",
44 "--jscomp_error=visibility",
45 "--language_in=ECMASCRIPT5_STRICT",
46 "--summary_detail_level=3",
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",
57 _DISABLED_CLOSURE_ARGS = [
58 # TODO(dbeam): happens when the same file is <include>d multiple times.
59 "--jscomp_off=duplicate",
60 # TODO(fukino): happens when cr.defineProperty() has a type annotation.
61 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562.
62 "--jscomp_off=misplacedTypeAnnotation",
65 _JAR_COMMAND = [
66 "java",
67 "-jar",
68 "-Xms1024m",
69 "-client",
70 "-XX:+TieredCompilation"
73 _found_java = False
75 def __init__(self, verbose=False, strict=False):
76 current_dir = os.path.join(os.path.dirname(__file__))
77 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar")
78 self._temp_files = []
79 self._verbose = verbose
80 self._strict = strict
81 self._error_filter = error_filter.PromiseErrorFilter()
83 def _clean_up(self):
84 if not self._temp_files:
85 return
87 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files))
88 for f in self._temp_files:
89 os.remove(f)
90 self._temp_files = []
92 def _debug(self, msg, error=False):
93 if self._verbose:
94 print "(INFO) %s" % msg
96 def _error(self, msg):
97 print >> sys.stderr, "(ERROR) %s" % msg
98 self._clean_up()
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
106 def _run_command(self, cmd):
107 """Runs a shell command.
109 Args:
110 cmd: A list of tokens to be joined into a shell command.
112 Return:
113 True if the exit code was 0, else False.
115 cmd_str = " ".join(cmd)
116 self._debug("Running command: %s" % cmd_str)
118 devnull = open(os.devnull, "w")
119 return subprocess.Popen(
120 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True)
122 def _check_java_path(self):
123 """Checks that `java` is on the system path."""
124 if not self._found_java:
125 proc = self._run_command(["which", "java"])
126 proc.communicate()
127 if proc.returncode == 0:
128 self._found_java = True
129 else:
130 self._error("Cannot find java (`which java` => %s)" % proc.returncode)
132 return self._found_java
134 def _run_jar(self, jar, args=None):
135 args = args or []
136 self._check_java_path()
137 return self._run_command(self._JAR_COMMAND + [jar] + args)
139 def _fix_line_number(self, match):
140 """Changes a line number from /tmp/file:300 to /orig/file:100.
142 Args:
143 match: A re.MatchObject from matching against a line number regex.
145 Returns:
146 The fixed up /file and :line number.
148 real_file = self._processor.get_file_from_line(match.group(1))
149 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number)
151 def _fix_up_error(self, error):
152 """Filter out irrelevant errors or fix line numbers.
154 Args:
155 error: A Closure compiler error (2 line string with error and source).
157 Return:
158 The fixed up error string (blank if it should be ignored).
160 if " first declared in " in error:
161 # Ignore "Variable x first declared in /same/file".
162 return ""
164 expanded_file = self._expanded_file
165 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error)
166 return fixed.replace(expanded_file, os.path.abspath(self._file_arg))
168 def _format_errors(self, errors):
169 """Formats Closure compiler errors to easily spot compiler output."""
170 errors = filter(None, errors)
171 contents = "\n## ".join("\n\n".join(errors).splitlines())
172 return "## %s" % contents if contents else ""
174 def _create_temp_file(self, contents):
175 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file:
176 self._temp_files.append(tmp_file.name)
177 tmp_file.write(contents)
178 return tmp_file.name
180 def run_js_check(self, sources, externs=None):
181 if not self._check_java_path():
182 return 1, ""
184 args = ["--js=%s" % s for s in sources]
185 if externs:
186 args += ["--externs=%s" % e for e in externs]
187 args_file_content = " %s" % " ".join(self._common_args() + args)
188 self._debug("Args: %s" % args_file_content.strip())
190 args_file = self._create_temp_file(args_file_content)
191 self._debug("Args file: %s" % args_file)
193 runner_args = ["--compiler-args-file=%s" % args_file]
194 runner_cmd = self._run_jar(self._runner_jar, args=runner_args)
195 _, stderr = runner_cmd.communicate()
197 errors = stderr.strip().split("\n\n")
198 self._debug("Summary: %s" % errors.pop())
200 self._clean_up()
202 return errors, stderr
204 def check(self, source_file, depends=None, externs=None):
205 """Closure compile a file and check for errors.
207 Args:
208 source_file: A file to check.
209 depends: Other files that would be included with a <script> earlier in
210 the page.
211 externs: @extern files that inform the compiler about custom globals.
213 Returns:
214 (has_errors, output) A boolean indicating if there were errors and the
215 Closure compiler output (as a string).
217 depends = depends or []
218 externs = externs or set()
220 if not self._check_java_path():
221 return 1, ""
223 self._debug("FILE: %s" % source_file)
225 if source_file.endswith("_externs.js"):
226 self._debug("Skipping externs: %s" % source_file)
227 return
229 self._file_arg = source_file
231 tmp_dir = tempfile.gettempdir()
232 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f)
234 includes = [rel_path(f) for f in depends + [source_file]]
235 contents = ['<include src="%s">' % i for i in includes]
236 meta_file = self._create_temp_file("\n".join(contents))
237 self._debug("Meta file: %s" % meta_file)
239 self._processor = processor.Processor(meta_file)
240 self._expanded_file = self._create_temp_file(self._processor.contents)
241 self._debug("Expanded file: %s" % self._expanded_file)
243 errors, stderr = self.run_js_check([self._expanded_file], externs)
245 # Filter out false-positive promise chain errors.
246 # See https://github.com/google/closure-compiler/issues/715 for details.
247 errors = self._error_filter.filter(errors);
249 output = self._format_errors(map(self._fix_up_error, errors))
250 if errors:
251 prefix = "\n" if output else ""
252 self._error("Error in: %s%s%s" % (source_file, prefix, output))
253 elif output:
254 self._debug("Output: %s" % output)
256 return bool(errors), output
258 def check_multiple(self, sources):
259 """Closure compile a set of files and check for errors.
261 Args:
262 sources: An array of files to check.
264 Returns:
265 (has_errors, output) A boolean indicating if there were errors and the
266 Closure compiler output (as a string).
269 errors, stderr = self.run_js_check(sources)
270 return bool(errors), stderr
272 if __name__ == "__main__":
273 parser = argparse.ArgumentParser(
274 description="Typecheck JavaScript using Closure compiler")
275 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE,
276 help="Path to a source file to typecheck")
277 single_file_group = parser.add_mutually_exclusive_group()
278 single_file_group.add_argument("--single-file", dest="single_file",
279 action="store_true",
280 help="Process each source file individually")
281 single_file_group.add_argument("--no-single-file", dest="single_file",
282 action="store_false",
283 help="Process all source files as a group")
284 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE)
285 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE)
286 parser.add_argument("-o", "--out_file", help="A place to output results")
287 parser.add_argument("-v", "--verbose", action="store_true",
288 help="Show more information as this script runs")
289 parser.add_argument("--strict", action="store_true",
290 help="Enable strict type checking")
291 parser.add_argument("--success-stamp",
292 help="Timestamp file to update upon success")
294 parser.set_defaults(single_file=True, strict=False)
295 opts = parser.parse_args()
297 depends = opts.depends or []
298 externs = opts.externs or set()
300 checker = Checker(verbose=opts.verbose, strict=opts.strict)
301 if opts.single_file:
302 for source in opts.sources:
303 depends, externs = build.inputs.resolve_recursive_dependencies(
304 source,
305 depends,
306 externs)
307 has_errors, _ = checker.check(source, depends=depends, externs=externs)
308 if has_errors:
309 sys.exit(1)
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("")
317 else:
318 has_errors, errors = checker.check_multiple(opts.sources)
319 if has_errors:
320 print errors
321 sys.exit(1)
323 if opts.success_stamp:
324 with open(opts.success_stamp, 'w'):
325 os.utime(opts.success_stamp, None)