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