Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(289)

Side by Side Diff: third_party/closure_compiler/checker.py

Issue 475493002: Closure compiler: add pydoc for parts of third_party/closure_compiler/. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: nits Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/closure_compiler/build/inputs.py ('k') | third_party/closure_compiler/processor.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 """A script to run Closure compiler on JavaScript file and check for errors."""
Tyler Breisacher (Chromium) 2014/08/13 19:59:42 nit: "Runs Closure Compiler on ..." "A script to"
Dan Beam 2014/08/13 20:23:45 Done.
7
6 import argparse 8 import argparse
7 import os 9 import os
8 import processor
9 import re 10 import re
10 import subprocess 11 import subprocess
11 import sys 12 import sys
12 import tempfile 13 import tempfile
14 import processor
Tyler Breisacher (Chromium) 2014/08/13 19:59:42 These are generally supposed to be alphabetical ri
Dan Beam 2014/08/13 20:23:45 processor is not a system import
13 15
14 16
15 class Checker(object): 17 class Checker(object):
16 _common_closure_args = [ 18 """A class to run the Closure compiler .jar on a given source file and return
19 the success/errors."""
20
21 _COMMON_CLOSURE_ARGS = [
17 "--accept_const_keyword", 22 "--accept_const_keyword",
18 "--language_in=ECMASCRIPT5", 23 "--language_in=ECMASCRIPT5",
19 "--summary_detail_level=3", 24 "--summary_detail_level=3",
20 "--warning_level=VERBOSE", 25 "--warning_level=VERBOSE",
21 "--jscomp_error=accessControls", 26 "--jscomp_error=accessControls",
22 "--jscomp_error=ambiguousFunctionDecl", 27 "--jscomp_error=ambiguousFunctionDecl",
23 "--jscomp_error=checkStructDictInheritance", 28 "--jscomp_error=checkStructDictInheritance",
24 "--jscomp_error=checkTypes", 29 "--jscomp_error=checkTypes",
25 "--jscomp_error=checkVars", 30 "--jscomp_error=checkVars",
26 "--jscomp_error=constantProperty", 31 "--jscomp_error=constantProperty",
27 "--jscomp_error=deprecated", 32 "--jscomp_error=deprecated",
28 "--jscomp_error=externsValidation", 33 "--jscomp_error=externsValidation",
29 "--jscomp_error=globalThis", 34 "--jscomp_error=globalThis",
30 "--jscomp_error=invalidCasts", 35 "--jscomp_error=invalidCasts",
31 "--jscomp_error=misplacedTypeAnnotation", 36 "--jscomp_error=misplacedTypeAnnotation",
32 "--jscomp_error=missingProperties", 37 "--jscomp_error=missingProperties",
33 "--jscomp_error=missingReturn", 38 "--jscomp_error=missingReturn",
34 "--jscomp_error=nonStandardJsDocs", 39 "--jscomp_error=nonStandardJsDocs",
35 "--jscomp_error=suspiciousCode", 40 "--jscomp_error=suspiciousCode",
36 "--jscomp_error=undefinedNames", 41 "--jscomp_error=undefinedNames",
37 "--jscomp_error=undefinedVars", 42 "--jscomp_error=undefinedVars",
38 "--jscomp_error=unknownDefines", 43 "--jscomp_error=unknownDefines",
39 "--jscomp_error=uselessCode", 44 "--jscomp_error=uselessCode",
40 "--jscomp_error=visibility", 45 "--jscomp_error=visibility",
41 # TODO(dbeam): happens when the same file is <include>d multiple times. 46 # TODO(dbeam): happens when the same file is <include>d multiple times.
42 "--jscomp_off=duplicate", 47 "--jscomp_off=duplicate",
43 ] 48 ]
44 49
45 _found_java = False 50 _JAR_COMMAND = [
46
47 _jar_command = [
48 "java", 51 "java",
49 "-jar", 52 "-jar",
50 "-Xms1024m", 53 "-Xms1024m",
51 "-server", 54 "-server",
Tyler Breisacher (Chromium) 2014/08/13 19:59:42 Side note: I believe -client is generally recommen
Dan Beam 2014/08/13 20:23:45 Acknowledged.
52 "-XX:+TieredCompilation" 55 "-XX:+TieredCompilation"
53 ] 56 ]
54 57
58 _found_java = False
Tyler Breisacher (Chromium) 2014/08/13 19:59:42 If I remember how Python works (which is a big if)
Dan Beam 2014/08/13 20:23:45 i don't want it to be an instance property
Tyler Breisacher (Chromium) 2014/08/13 20:27:01 It looks like it's being used that way at line 102
59
55 def __init__(self, verbose=False): 60 def __init__(self, verbose=False):
56 current_dir = os.path.join(os.path.dirname(__file__)) 61 current_dir = os.path.join(os.path.dirname(__file__))
57 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") 62 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar")
58 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") 63 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar")
59 self._temp_files = [] 64 self._temp_files = []
60 self._verbose = verbose 65 self._verbose = verbose
61 66
62 def _clean_up(self): 67 def _clean_up(self):
63 if not self._temp_files: 68 if not self._temp_files:
64 return 69 return
65 70
66 self._debug("Deleting temporary files: " + ", ".join(self._temp_files)) 71 self._debug("Deleting temporary files: " + ", ".join(self._temp_files))
67 for f in self._temp_files: 72 for f in self._temp_files:
68 os.remove(f) 73 os.remove(f)
69 self._temp_files = [] 74 self._temp_files = []
70 75
71 def _debug(self, msg, error=False): 76 def _debug(self, msg, error=False):
72 if self._verbose: 77 if self._verbose:
73 print "(INFO) " + msg 78 print "(INFO) " + msg
74 79
75 def _error(self, msg): 80 def _error(self, msg):
76 print >> sys.stderr, "(ERROR) " + msg 81 print >> sys.stderr, "(ERROR) " + msg
77 self._clean_up() 82 self._clean_up()
78 83
79 def _run_command(self, cmd): 84 def _run_command(self, cmd):
85 """Runs a shell command.
86
87 Args:
88 cmd: A list of tokens to be joined into a shell command.
89
90 Return:
91 True if the exit code was 0, else False.
92 """
80 cmd_str = " ".join(cmd) 93 cmd_str = " ".join(cmd)
81 self._debug("Running command: " + cmd_str) 94 self._debug("Running command: " + cmd_str)
82 95
83 devnull = open(os.devnull, "w") 96 devnull = open(os.devnull, "w")
84 return subprocess.Popen( 97 return subprocess.Popen(
85 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) 98 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True)
86 99
87 def _check_java_path(self): 100 def _check_java_path(self):
101 """Checks that `java` is on the system path."""
88 if not self._found_java: 102 if not self._found_java:
89 proc = self._run_command(["which", "java"]) 103 proc = self._run_command(["which", "java"])
90 proc.communicate() 104 proc.communicate()
91 if proc.returncode == 0: 105 if proc.returncode == 0:
92 self._found_java = True 106 self._found_java = True
93 else: 107 else:
94 self._error("Cannot find java (`which java` => %s)" % proc.returncode) 108 self._error("Cannot find java (`which java` => %s)" % proc.returncode)
95 109
96 return self._found_java 110 return self._found_java
97 111
98 def _run_jar(self, jar, args=[]): 112 def _run_jar(self, jar, args=None):
113 args = args or []
99 self._check_java_path() 114 self._check_java_path()
100 return self._run_command(self._jar_command + [jar] + args) 115 return self._run_command(self._JAR_COMMAND + [jar] + args)
101 116
102 def _fix_line_number(self, match): 117 def _fix_line_number(self, match):
118 """Changes a line number from /tmp/file:300 to /orig/file:100.
119
120 Args:
121 match: A re.MatchObject from matching against a line number regex.
122
123 Returns:
124 The fixed up /file and :line number.
125 """
103 real_file = self._processor.get_file_from_line(match.group(1)) 126 real_file = self._processor.get_file_from_line(match.group(1))
104 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) 127 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number)
105 128
106 def _fix_up_error(self, error): 129 def _fix_up_error(self, error):
130 """Filter out irrelevant errors or fix line numbers.
131
132 Args:
133 error: An Closure compiler error (2 line string with error and source).
Tyler Breisacher (Chromium) 2014/08/13 19:59:41 s/An/A/
Dan Beam 2014/08/13 20:23:45 Done.
134
135 Return:
136 The fixed up erorr string (blank if it should be ignored).
137 """
107 if " first declared in " in error: 138 if " first declared in " in error:
108 # Ignore "Variable x first declared in /same/file". 139 # Ignore "Variable x first declared in /same/file".
109 return "" 140 return ""
110 141
111 file = self._expanded_file 142 expanded_file = self._expanded_file
112 fixed = re.sub("%s:(\d+)" % file, self._fix_line_number, error) 143 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error)
113 return fixed.replace(file, os.path.abspath(self._file_arg)) 144 return fixed.replace(expanded_file, os.path.abspath(self._file_arg))
114 145
115 def _format_errors(self, errors): 146 def _format_errors(self, errors):
147 """Formats Closure compiler errors to easily spot compiler output."""
116 errors = filter(None, errors) 148 errors = filter(None, errors)
117 contents = ("\n" + "## ").join("\n\n".join(errors).splitlines()) 149 contents = ("\n" + "## ").join("\n\n".join(errors).splitlines())
118 return "## " + contents if contents else "" 150 return "## " + contents if contents else ""
119 151
120 def _create_temp_file(self, contents): 152 def _create_temp_file(self, contents):
121 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: 153 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file:
122 self._temp_files.append(tmp_file.name) 154 self._temp_files.append(tmp_file.name)
123 tmp_file.write(contents) 155 tmp_file.write(contents)
124 return tmp_file.name 156 return tmp_file.name
125 157
126 def check(self, file, depends=[], externs=[]): 158 def check(self, source_file, depends=None, externs=None):
159 """Closure compile a file and check for errors.
160
161 Args:
162 source_file: A file to check.
163 depends: Other files that would be included with a <script> earlier in
164 the page.
165 externs: @extern files that inform the compiler about custom globals.
166
167 Returns:
168 (exitcode, output) The exit code of the Closure compiler (as a number)
169 and its output (as a string).
170 """
171 depends = depends or []
172 externs = externs or []
173
127 if not self._check_java_path(): 174 if not self._check_java_path():
128 return 1, "" 175 return 1, ""
129 176
130 self._debug("FILE: " + file) 177 self._debug("FILE: " + source_file)
131 178
132 if file.endswith("_externs.js"): 179 if source_file.endswith("_externs.js"):
133 self._debug("Skipping externs: " + file) 180 self._debug("Skipping externs: " + source_file)
134 return 181 return
135 182
136 self._file_arg = file 183 self._file_arg = source_file
137 184
138 tmp_dir = tempfile.gettempdir() 185 tmp_dir = tempfile.gettempdir()
139 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)
140 187
141 contents = ['<include src="%s">' % rel_path(f) for f in depends + [file]] 188 includes = [rel_path(f) for f in depends + [source_file]]
189 contents = ['<include src="%s">' % i for i in includes]
142 meta_file = self._create_temp_file("\n".join(contents)) 190 meta_file = self._create_temp_file("\n".join(contents))
143 self._debug("Meta file: " + meta_file) 191 self._debug("Meta file: " + meta_file)
144 192
145 self._processor = processor.Processor(meta_file) 193 self._processor = processor.Processor(meta_file)
146 self._expanded_file = self._create_temp_file(self._processor.contents) 194 self._expanded_file = self._create_temp_file(self._processor.contents)
147 self._debug("Expanded file: " + self._expanded_file) 195 self._debug("Expanded file: " + self._expanded_file)
148 196
149 args = ["--js=" + self._expanded_file] + ["--externs=" + e for e in externs] 197 args = ["--js=" + self._expanded_file] + ["--externs=" + e for e in externs]
150 args_file_content = " " + " ".join(self._common_closure_args + args) 198 args_file_content = " " + " ".join(self._COMMON_CLOSURE_ARGS + args)
151 self._debug("Args: " + args_file_content.strip()) 199 self._debug("Args: " + args_file_content.strip())
152 200
153 args_file = self._create_temp_file(args_file_content) 201 args_file = self._create_temp_file(args_file_content)
154 self._debug("Args file: " + args_file) 202 self._debug("Args file: " + args_file)
155 203
156 runner_args = ["--compiler-args-file=" + args_file] 204 runner_args = ["--compiler-args-file=" + args_file]
157 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) 205 runner_cmd = self._run_jar(self._runner_jar, args=runner_args)
158 (_, stderr) = runner_cmd.communicate() 206 (_, stderr) = runner_cmd.communicate()
159 207
160 errors = stderr.strip().split("\n\n") 208 errors = stderr.strip().split("\n\n")
161 self._debug("Summary: " + errors.pop()) 209 self._debug("Summary: " + errors.pop())
162 210
163 output = self._format_errors(map(self._fix_up_error, errors)) 211 output = self._format_errors(map(self._fix_up_error, errors))
164 if runner_cmd.returncode: 212 if runner_cmd.returncode:
165 self._error("Error in: " + file + ("\n" + output if output else "")) 213 self._error("Error in: " + source_file + ("\n" + output if output else "") )
166 elif output: 214 elif output:
167 self._debug("Output: " + output) 215 self._debug("Output: " + output)
168 216
169 self._clean_up() 217 self._clean_up()
170 218
171 return runner_cmd.returncode, output 219 return runner_cmd.returncode, output
172 220
173 221
174 if __name__ == "__main__": 222 if __name__ == "__main__":
175 parser = argparse.ArgumentParser( 223 parser = argparse.ArgumentParser(
(...skipping 11 matching lines...) Expand all
187 for source in opts.sources: 235 for source in opts.sources:
188 if not checker.check(source, depends=opts.depends, externs=opts.externs): 236 if not checker.check(source, depends=opts.depends, externs=opts.externs):
189 sys.exit(1) 237 sys.exit(1)
190 238
191 if opts.out_file: 239 if opts.out_file:
192 out_dir = os.path.dirname(opts.out_file) 240 out_dir = os.path.dirname(opts.out_file)
193 if not os.path.exists(out_dir): 241 if not os.path.exists(out_dir):
194 os.makedirs(out_dir) 242 os.makedirs(out_dir)
195 # TODO(dbeam): write compiled file to |opts.out_file|. 243 # TODO(dbeam): write compiled file to |opts.out_file|.
196 open(opts.out_file, "w").write("") 244 open(opts.out_file, "w").write("")
OLDNEW
« no previous file with comments | « third_party/closure_compiler/build/inputs.py ('k') | third_party/closure_compiler/processor.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698