1# Copyright (c) 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import os
6import re
7import sys
8import time
9
10import checklicenses
11
12from tracing import tracing_project
13
14
15def _FormatError(msg, files):
16  return ('%s in these files:\n' % msg +
17          '\n'.join('  ' + x for x in files))
18
19
20def _ReportErrorFileAndLine(filename, line_num, dummy_line):
21  """Default error formatter for _FindNewViolationsOfRule."""
22  return '%s:%s' % (filename, line_num)
23
24
25def _FindNewViolationsOfRule(callable_rule, input_api,
26                             error_formatter=_ReportErrorFileAndLine):
27  """Find all newly introduced violations of a per-line rule (a callable).
28
29  Arguments:
30    callable_rule: a callable taking a file extension and line of input and
31      returning True if the rule is satisfied and False if there was a problem.
32    input_api: object to enumerate the affected files.
33    source_file_filter: a filter to be passed to the input api.
34    error_formatter: a callable taking (filename, line_number, line) and
35      returning a formatted error string.
36
37  Returns:
38    A list of the newly-introduced violations reported by the rule.
39  """
40  errors = []
41  for f in input_api.AffectedFiles(include_deletes=False):
42    # For speed, we do two passes, checking first the full file.  Shelling out
43    # to the SCM to determine the changed region can be quite expensive on
44    # Win32.  Assuming that most files will be kept problem-free, we can
45    # skip the SCM operations most of the time.
46    extension = str(f.LocalPath()).rsplit('.', 1)[-1]
47    if all(callable_rule(extension, line) for line in f.NewContents()):
48      continue  # No violation found in full text: can skip considering diff.
49
50    if tracing_project.TracingProject.IsIgnoredFile(f):
51      continue
52
53    for line_num, line in f.ChangedContents():
54      if not callable_rule(extension, line):
55        errors.append(error_formatter(f.LocalPath(), line_num, line))
56
57  return errors
58
59
60def CheckCopyright(input_api):
61  results = []
62  results += _CheckCopyrightThirdParty(input_api)
63  results += _CheckCopyrightNonThirdParty(input_api)
64  return results
65
66
67def _CheckCopyrightThirdParty(input_api):
68  results = []
69  has_third_party_change = any(
70      tracing_project.TracingProject.IsThirdParty(f)
71      for f in input_api.AffectedFiles(include_deletes=False))
72  if has_third_party_change:
73    tracing_root = os.path.abspath(
74        os.path.join(os.path.dirname(__file__), '..'))
75    tracing_third_party = os.path.join(tracing_root, 'tracing', 'third_party')
76    has_invalid_license = checklicenses.check_licenses(
77        tracing_root, tracing_third_party)
78    if has_invalid_license:
79      results.append(
80          'License check encountered invalid licenses in tracing/third_party/.')
81  return results
82
83
84def _CheckCopyrightNonThirdParty(input_api):
85  project_name = 'Chromium'
86
87  current_year = int(time.strftime('%Y'))
88  allow_old_years=True
89  if allow_old_years:
90    allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
91  else:
92    allowed_years = [str(current_year)]
93  years_re = '(' + '|'.join(allowed_years) + ')'
94
95  # The (c) is deprecated, but tolerate it until it's removed from all files.
96  non_html_license_header = (
97      r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
98        r'All rights reserved\.\n'
99      r'.*? Use of this source code is governed by a BSD-style license that '
100        r'can be\n'
101      r'.*? found in the LICENSE file\.(?: \*/)?\n'
102  ) % {
103      'year': years_re,
104      'project': project_name,
105  }
106  non_html_license_re = re.compile(non_html_license_header, re.MULTILINE)
107
108  html_license_header = (
109      r'^Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
110        r'All rights reserved\.\n'
111      r'Use of this source code is governed by a BSD-style license that '
112        r'can be\n'
113      r'found in the LICENSE file\.(?: \*/)?\n'
114  ) % {
115      'year': years_re,
116      'project': project_name,
117  }
118  html_license_re = re.compile(html_license_header, re.MULTILINE)
119
120  sources = list(s for s in input_api.AffectedFiles(include_deletes=False)
121                 if not tracing_project.TracingProject.IsThirdParty(s))
122
123  html_sources = [f for f in sources
124                  if os.path.splitext(f.LocalPath())[1] == '.html']
125  non_html_sources = [f for f in sources
126                      if os.path.splitext(f.LocalPath())[1] != '.html']
127
128  results = []
129  results += _Check(input_api, html_license_re, html_sources)
130  results += _Check(input_api, non_html_license_re, non_html_sources)
131  return results
132
133
134def _Check(input_api, license_re, sources):
135  bad_files = []
136  for f in sources:
137    if tracing_project.TracingProject.IsIgnoredFile(f):
138      continue
139    contents = '\n'.join(f.NewContents())
140    if not license_re.search(contents):
141      bad_files.append(f.LocalPath())
142  if bad_files:
143    return [_FormatError(
144        'License must match:\n%s\n' % license_re.pattern +
145        'Found a bad license header',
146        bad_files)]
147  return []
148
149
150def CheckLongLines(input_api, maxlen=80):
151  """Checks the line length in all text files to be submitted."""
152  maxlens = {
153      '': maxlen,
154  }
155
156  # Language specific exceptions to max line length.
157  # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
158  # superset of CPP_EXCEPTIONS.
159  CPP_FILE_EXTS = ('c', 'cc')
160  CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
161  JAVA_FILE_EXTS = ('java',)
162  JAVA_EXCEPTIONS = ('import ', 'package ')
163  OBJC_FILE_EXTS = ('h', 'm', 'mm')
164  OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
165                     '#pragma')
166
167  LANGUAGE_EXCEPTIONS = [
168      (CPP_FILE_EXTS, CPP_EXCEPTIONS),
169      (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
170      (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
171  ]
172
173  def no_long_lines(file_extension, line):
174    # Check for language specific exceptions.
175    if any(file_extension in exts and line.startswith(exceptions)
176           for exts, exceptions in LANGUAGE_EXCEPTIONS):
177      return True
178
179    file_maxlen = maxlens.get(file_extension, maxlens[''])
180    # Stupidly long symbols that needs to be worked around if takes 66% of line.
181    long_symbol = file_maxlen * 2 / 3
182    # Hard line length limit at 50% more.
183    extra_maxlen = file_maxlen * 3 / 2
184
185    line_len = len(line)
186    if line_len <= file_maxlen:
187      return True
188
189    if '@suppress longLineCheck' in line:
190      return True
191
192    if line_len > extra_maxlen:
193      return False
194
195    if any((url in line) for url in ('file://', 'http://', 'https://')):
196      return True
197
198    if 'url(' in line and file_extension == 'css':
199      return True
200
201    if '<include' in line and file_extension in ('css', 'html', 'js'):
202      return True
203
204    return re.match(
205        r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
206
207  def format_error(filename, line_num, line):
208    return '%s, line %s, %s chars' % (filename, line_num, len(line))
209
210  errors = _FindNewViolationsOfRule(no_long_lines, input_api,
211                                    error_formatter=format_error)
212  if errors:
213    return [_FormatError(
214        'Found lines longer than %s characters' % maxlen,
215        errors)]
216  else:
217    return []
218
219def CheckChangeLogBug(input_api):
220  results = []
221  if input_api.change.BUG is None or re.match('\#\d+$', input_api.change.BUG):
222    return []
223  return [('Invalid bug "%s". BUG= should either not be present or start'
224           ' with # for a github issue.' % input_api.change.BUG)]
225
226
227def RunChecks(input_api):
228  results = []
229  results += CheckCopyright(input_api)
230  results += CheckLongLines(input_api)
231  results += CheckChangeLogBug(input_api)
232  return results
233