OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright 2007 The Closure Linter Authors. All Rights Reserved. | |
3 # | |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 # you may not use this file except in compliance with the License. | |
6 # You may obtain a copy of the License at | |
7 # | |
8 # http://www.apache.org/licenses/LICENSE-2.0 | |
9 # | |
10 # Unless required by applicable law or agreed to in writing, software | |
11 # distributed under the License is distributed on an "AS-IS" BASIS, | |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 # See the License for the specific language governing permissions and | |
14 # limitations under the License. | |
15 | |
16 """Checks JavaScript files for common style guide violations. | |
17 | |
18 gjslint.py is designed to be used as a PRESUBMIT script to check for javascript | |
19 style guide violations. As of now, it checks for the following violations: | |
20 | |
21 * Missing and extra spaces | |
22 * Lines longer than 80 characters | |
23 * Missing newline at end of file | |
24 * Missing semicolon after function declaration | |
25 * Valid JsDoc including parameter matching | |
26 | |
27 Someday it will validate to the best of its ability against the entirety of the | |
28 JavaScript style guide. | |
29 | |
30 This file is a front end that parses arguments and flags. The core of the code | |
31 is in tokenizer.py and checker.py. | |
32 """ | |
33 | |
34 __author__ = ('robbyw@google.com (Robert Walker)', | |
35 'ajp@google.com (Andy Perelson)', | |
36 'nnaze@google.com (Nathan Naze)',) | |
37 | |
38 import errno | |
39 import itertools | |
40 import os | |
41 import platform | |
42 import re | |
43 import sys | |
44 import time | |
45 | |
46 import gflags as flags | |
47 | |
48 from closure_linter import errorrecord | |
49 from closure_linter import runner | |
50 from closure_linter.common import erroraccumulator | |
51 from closure_linter.common import simplefileflags as fileflags | |
52 | |
53 # Attempt import of multiprocessing (should be available in Python 2.6 and up). | |
54 try: | |
55 # pylint: disable=g-import-not-at-top | |
56 import multiprocessing | |
57 except ImportError: | |
58 multiprocessing = None | |
59 | |
60 FLAGS = flags.FLAGS | |
61 flags.DEFINE_boolean('unix_mode', False, | |
62 'Whether to emit warnings in standard unix format.') | |
63 flags.DEFINE_boolean('beep', True, 'Whether to beep when errors are found.') | |
64 flags.DEFINE_boolean('time', False, 'Whether to emit timing statistics.') | |
65 flags.DEFINE_boolean('quiet', False, 'Whether to minimize logged messages. ' | |
66 'Most useful for per-file linting, such as that performed ' | |
67 'by the presubmit linter service.') | |
68 flags.DEFINE_boolean('check_html', False, | |
69 'Whether to check javascript in html files.') | |
70 flags.DEFINE_boolean('summary', False, | |
71 'Whether to show an error count summary.') | |
72 flags.DEFINE_list('additional_extensions', None, 'List of additional file ' | |
73 'extensions (not js) that should be treated as ' | |
74 'JavaScript files.') | |
75 flags.DEFINE_boolean('multiprocess', | |
76 platform.system() is 'Linux' and bool(multiprocessing), | |
77 'Whether to attempt parallelized linting using the ' | |
78 'multiprocessing module. Enabled by default on Linux ' | |
79 'if the multiprocessing module is present (Python 2.6+). ' | |
80 'Otherwise disabled by default. ' | |
81 'Disabling may make debugging easier.') | |
82 flags.ADOPT_module_key_flags(fileflags) | |
83 flags.ADOPT_module_key_flags(runner) | |
84 | |
85 | |
86 GJSLINT_ONLY_FLAGS = ['--unix_mode', '--beep', '--nobeep', '--time', | |
87 '--check_html', '--summary', '--quiet'] | |
88 | |
89 | |
90 | |
91 def _MultiprocessCheckPaths(paths): | |
92 """Run _CheckPath over mutltiple processes. | |
93 | |
94 Tokenization, passes, and checks are expensive operations. Running in a | |
95 single process, they can only run on one CPU/core. Instead, | |
96 shard out linting over all CPUs with multiprocessing to parallelize. | |
97 | |
98 Args: | |
99 paths: paths to check. | |
100 | |
101 Yields: | |
102 errorrecord.ErrorRecords for any found errors. | |
103 """ | |
104 | |
105 pool = multiprocessing.Pool() | |
106 | |
107 path_results = pool.imap(_CheckPath, paths) | |
108 for results in path_results: | |
109 for result in results: | |
110 yield result | |
111 | |
112 # Force destruct before returning, as this can sometimes raise spurious | |
113 # "interrupted system call" (EINTR), which we can ignore. | |
114 try: | |
115 pool.close() | |
116 pool.join() | |
117 del pool | |
118 except OSError as err: | |
119 if err.errno is not errno.EINTR: | |
120 raise err | |
121 | |
122 | |
123 def _CheckPaths(paths): | |
124 """Run _CheckPath on all paths in one thread. | |
125 | |
126 Args: | |
127 paths: paths to check. | |
128 | |
129 Yields: | |
130 errorrecord.ErrorRecords for any found errors. | |
131 """ | |
132 | |
133 for path in paths: | |
134 results = _CheckPath(path) | |
135 for record in results: | |
136 yield record | |
137 | |
138 | |
139 def _CheckPath(path): | |
140 """Check a path and return any errors. | |
141 | |
142 Args: | |
143 path: paths to check. | |
144 | |
145 Returns: | |
146 A list of errorrecord.ErrorRecords for any found errors. | |
147 """ | |
148 | |
149 error_handler = erroraccumulator.ErrorAccumulator() | |
150 runner.Run(path, error_handler) | |
151 | |
152 make_error_record = lambda err: errorrecord.MakeErrorRecord(path, err) | |
153 return map(make_error_record, error_handler.GetErrors()) | |
154 | |
155 | |
156 def _GetFilePaths(argv): | |
157 suffixes = ['.js'] | |
158 if FLAGS.additional_extensions: | |
159 suffixes += ['.%s' % ext for ext in FLAGS.additional_extensions] | |
160 if FLAGS.check_html: | |
161 suffixes += ['.html', '.htm'] | |
162 return fileflags.GetFileList(argv, 'JavaScript', suffixes) | |
163 | |
164 | |
165 # Error printing functions | |
166 | |
167 | |
168 def _PrintFileSummary(paths, records): | |
169 """Print a detailed summary of the number of errors in each file.""" | |
170 | |
171 paths = list(paths) | |
172 paths.sort() | |
173 | |
174 for path in paths: | |
175 path_errors = [e for e in records if e.path == path] | |
176 print '%s: %d' % (path, len(path_errors)) | |
177 | |
178 | |
179 def _PrintFileSeparator(path): | |
180 print '----- FILE : %s -----' % path | |
181 | |
182 | |
183 def _PrintSummary(paths, error_records): | |
184 """Print a summary of the number of errors and files.""" | |
185 | |
186 error_count = len(error_records) | |
187 all_paths = set(paths) | |
188 all_paths_count = len(all_paths) | |
189 | |
190 if error_count is 0: | |
191 print '%d files checked, no errors found.' % all_paths_count | |
192 | |
193 new_error_count = len([e for e in error_records if e.new_error]) | |
194 | |
195 error_paths = set([e.path for e in error_records]) | |
196 error_paths_count = len(error_paths) | |
197 no_error_paths_count = all_paths_count - error_paths_count | |
198 | |
199 if (error_count or new_error_count) and not FLAGS.quiet: | |
200 error_noun = 'error' if error_count == 1 else 'errors' | |
201 new_error_noun = 'error' if new_error_count == 1 else 'errors' | |
202 error_file_noun = 'file' if error_paths_count == 1 else 'files' | |
203 ok_file_noun = 'file' if no_error_paths_count == 1 else 'files' | |
204 print ('Found %d %s, including %d new %s, in %d %s (%d %s OK).' % | |
205 (error_count, | |
206 error_noun, | |
207 new_error_count, | |
208 new_error_noun, | |
209 error_paths_count, | |
210 error_file_noun, | |
211 no_error_paths_count, | |
212 ok_file_noun)) | |
213 | |
214 | |
215 def _PrintErrorRecords(error_records): | |
216 """Print error records strings in the expected format.""" | |
217 | |
218 current_path = None | |
219 for record in error_records: | |
220 | |
221 if current_path != record.path: | |
222 current_path = record.path | |
223 if not FLAGS.unix_mode: | |
224 _PrintFileSeparator(current_path) | |
225 | |
226 print record.error_string | |
227 | |
228 | |
229 def _FormatTime(t): | |
230 """Formats a duration as a human-readable string. | |
231 | |
232 Args: | |
233 t: A duration in seconds. | |
234 | |
235 Returns: | |
236 A formatted duration string. | |
237 """ | |
238 if t < 1: | |
239 return '%dms' % round(t * 1000) | |
240 else: | |
241 return '%.2fs' % t | |
242 | |
243 | |
244 | |
245 | |
246 def main(argv=None): | |
247 """Main function. | |
248 | |
249 Args: | |
250 argv: Sequence of command line arguments. | |
251 """ | |
252 if argv is None: | |
253 argv = flags.FLAGS(sys.argv) | |
254 | |
255 if FLAGS.time: | |
256 start_time = time.time() | |
257 | |
258 # Emacs sets the environment variable INSIDE_EMACS in the subshell. | |
259 # Request Unix mode as emacs will expect output to be in Unix format | |
260 # for integration. | |
261 # See https://www.gnu.org/software/emacs/manual/html_node/emacs/ | |
262 # Interactive-Shell.html | |
263 if 'INSIDE_EMACS' in os.environ: | |
264 FLAGS.unix_mode = True | |
265 | |
266 suffixes = ['.js'] | |
267 if FLAGS.additional_extensions: | |
268 suffixes += ['.%s' % ext for ext in FLAGS.additional_extensions] | |
269 if FLAGS.check_html: | |
270 suffixes += ['.html', '.htm'] | |
271 paths = fileflags.GetFileList(argv, 'JavaScript', suffixes) | |
272 | |
273 if FLAGS.multiprocess: | |
274 records_iter = _MultiprocessCheckPaths(paths) | |
275 else: | |
276 records_iter = _CheckPaths(paths) | |
277 | |
278 records_iter, records_iter_copy = itertools.tee(records_iter, 2) | |
279 _PrintErrorRecords(records_iter_copy) | |
280 | |
281 error_records = list(records_iter) | |
282 _PrintSummary(paths, error_records) | |
283 | |
284 exit_code = 0 | |
285 | |
286 # If there are any errors | |
287 if error_records: | |
288 exit_code += 1 | |
289 | |
290 # If there are any new errors | |
291 if [r for r in error_records if r.new_error]: | |
292 exit_code += 2 | |
293 | |
294 if exit_code: | |
295 if FLAGS.summary: | |
296 _PrintFileSummary(paths, error_records) | |
297 | |
298 if FLAGS.beep: | |
299 # Make a beep noise. | |
300 sys.stdout.write(chr(7)) | |
301 | |
302 # Write out instructions for using fixjsstyle script to fix some of the | |
303 # reported errors. | |
304 fix_args = [] | |
305 for flag in sys.argv[1:]: | |
306 for f in GJSLINT_ONLY_FLAGS: | |
307 if flag.startswith(f): | |
308 break | |
309 else: | |
310 fix_args.append(flag) | |
311 | |
312 if not FLAGS.quiet: | |
313 print """ | |
314 Some of the errors reported by GJsLint may be auto-fixable using the script | |
315 fixjsstyle. Please double check any changes it makes and report any bugs. The | |
316 script can be run by executing: | |
317 | |
318 fixjsstyle %s """ % ' '.join(fix_args) | |
319 | |
320 if FLAGS.time: | |
321 print 'Done in %s.' % _FormatTime(time.time() - start_time) | |
322 | |
323 sys.exit(exit_code) | |
324 | |
325 | |
326 if __name__ == '__main__': | |
327 main() | |
OLD | NEW |