# Copyright (c) 2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import re
import subprocess
import os

try:
  from internal import internal_presubmit
except ImportError:
  internal_presubmit = None


SOURCE_FILE_EXTENSIONS = [
        '.c', '.cc', '.cpp', '.h', '.m', '.mm', '.py', '.mk', '.am', '.json',
        '.gyp', '.gypi'
        ]

EXCLUDED_PATHS = []

# Finds what seem to be definitions of DllRegisterServer.
DLL_REGISTER_SERVER_RE = re.compile('\s*STDAPI\s+DllRegisterServer\s*\(')

# Matches a Tracker story URL
story_url_re = re.compile('https?://tracker.+/[0-9]+')

# Matches filenames in which we allow tabs
tabs_ok_re = re.compile('.*\.(vcproj|vsprops|sln)$')

# Matches filenames of source files
source_files_re = re.compile('.*\.(cc|h|py|js)$')


# The top-level source directory.
_SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))


def CheckChange(input_api, output_api, committing, is_chrome_frame=False):
  results = []

  do_not_submit_errors = input_api.canned_checks.CheckDoNotSubmit(input_api,
                                                                  output_api)
  if committing:
    results += do_not_submit_errors
  elif do_not_submit_errors:
    results += [output_api.PresubmitNotifyResult(
        'There is a DO-NOT-SUBMIT issue')]

  results += CheckChangeHasNoTabs(input_api, output_api)
  results += CheckLongLines(input_api, output_api)
  results += CheckHasStoryOrBug(input_api, output_api)
  results += LocalChecks(input_api, output_api)
  results += WarnOnAtlSmartPointers(input_api, output_api)
  if not is_chrome_frame:
    results += CheckNoDllRegisterServer(input_api, output_api)
    results += CheckUnittestsRan(input_api, output_api, committing)
    if internal_presubmit:
      results += internal_presubmit.InternalChecks(input_api, output_api,
                                                   committing)
  return results


def CheckHasStoryOrBug(input_api, output_api):
  """We require either BUG= to be present and non-empty.  For
  a completely trivial change, use BUG=none.
  """
  if not ('BUG' in input_api.change.tags):
    return [output_api.PresubmitError('A BUG= tag is required. For trivial '
                                      'changes you can use BUG=none.')]
  if ('BUG' in input_api.change.tags and
      len(input_api.change.tags['BUG']) == 0):
    return [output_api.PresubmitError('A non-empty BUG= is required. For '
                                      'trivial changes you can use BUG=none.')]
  return []


def CheckChangeHasNoTabs(input_api, output_api):
  """Slightly modified version of the canned check with the same name.

  This version ignores certain file types in which we allow tabs.
  """
  for f, line_num, line in input_api.RightHandSideLines():
    if tabs_ok_re.match(f.LocalPath()):
      continue
    if '\t' in line:
      return [output_api.PresubmitError(
          "Found a tab character in %s, line %s" %
          (f.LocalPath(), line_num))]
  return []


def CheckLongLines(input_api, output_api, maxlen=80):
  """Checks that there aren't any lines longer than maxlen characters in any of
  the text files to be submitted.
  """
  basename = input_api.basename

  bad = []
  for f, line_num, line in input_api.RightHandSideLines():
    if not source_files_re.match(f.LocalPath()):
      continue
    if line.find('http://') != -1:
      # Exemption for long URLs
      continue
    if line.endswith('\n'):
      line = line[:-1]
    if len(line) > maxlen:
      bad.append(
          '%s, line %s, %s chars' %
          (basename(f.LocalPath()), line_num, len(line)))
      if len(bad) == 5:  # Just show the first 5 errors.
        break

  if bad:
    msg = "Found lines longer than %s characters (first 5 shown)." % maxlen
    return [output_api.PresubmitPromptWarning(msg, items=bad)]
  else:
    return []


_UNITTEST_MESSAGE = '''\
You must build and run the CEEE smoke tests before submitting. To clear this
error, run the script "smoke_test.bat" in the CEEE directory.
'''


def CheckUnittestsRan(input_api, output_api, committing):
  '''Checks that the unittests success file is newer than any modified file'''
  # But only if there were IE files modified, since we only have unit tests
  # for CEEE IE.
  files = []
  ie_paths_re = re.compile('ceee[\\\\/](ie|common)[\\\\/]')
  for f in input_api.AffectedFiles(include_deletes = False):
    path = f.LocalPath()
    if (ie_paths_re.match(path)):
      files.append(f)

  if not files:
    return []

  def MakeResult(message, modified_files=[]):
    if committing:
      return output_api.PresubmitError(message, modified_files)
    else:
      return output_api.PresubmitNotifyResult(message, modified_files)
  os_path = input_api.os_path

  success_files = [
      os_path.join(input_api.PresubmitLocalPath(),
                    '../chrome/Debug/ceee.success'),
      os_path.join(input_api.PresubmitLocalPath(),
                   '../chrome/Release/ceee.success')]

  if (not os_path.exists(success_files[0]) or
      not os_path.exists(success_files[1])):
    return [MakeResult(_UNITTEST_MESSAGE)]

  success_time = min(os.stat(success_files[0]).st_mtime,
                     os.stat(success_files[1]).st_mtime)
  modified_files = []
  for f in modified_files:
    file_time = os.stat(f.AbsoluteLocalPath()).st_mtime
    if file_time > success_time:
      modified_files.append(f.LocalPath())

  result = []
  if modified_files:
    result.append(MakeResult('These files have been modified since Debug and/or'
                             ' Release unittests were built.', modified_files))

  return result


def CheckNoDllRegisterServer(input_api, output_api):
  for f, line_num, line in input_api.RightHandSideLines():
    if DLL_REGISTER_SERVER_RE.search(line):
      file_name = os.path.basename(f.LocalPath())
      if file_name not in ['install_utils.h', 'install_utils_unittest.cc']:
        return [output_api.PresubmitError(
            '%s contains a definition of DllRegisterServer at line %s.\n'
            'Please search for CEEE_DEFINE_DLL_REGISTER_SERVER.' %
            (f.LocalPath(), line_num))]
  return []


def WarnOnAtlSmartPointers(input_api, output_api):
  smart_pointer_re = re.compile(r'\bCCom(Ptr|BSTR|Variant)\b')
  bad_files = []
  for f in input_api.AffectedFiles(include_deletes=False):
    contents = input_api.ReadFile(f, 'r')
    if smart_pointer_re.search(contents):
      bad_files.append(f.LocalPath())
  if bad_files:
    return [output_api.PresubmitPromptWarning(
        'The following files use CComPtr, CComBSTR and/or CComVariant.\n'
        'Please consider switching to ScopedComPtr, ScopedBstr and/or\n'
        'ScopedVariant as per team policy. (NOTE: Will soon be an error.)',
        items=bad_files)]
  else:
    return []


def LocalChecks(input_api, output_api, max_cols=80):
  """Reports an error if for any source file in SOURCE_FILE_EXTENSIONS:
     - uses CR (or CRLF)
     - contains a TAB
     - has a line that ends with whitespace
     - contains a line >|max_cols| cols unless |max_cols| is 0.
     - File does not end in a newline, or ends in more than one.

  Note that the whole file is checked, not only the changes.
  """
  C_SOURCE_FILE_EXTENSIONS = ('.c', '.cc', '.cpp', '.h', '.inl')
  cr_files = []
  eof_files = []
  results = []
  excluded_paths = [input_api.re.compile(x) for x in EXCLUDED_PATHS]
  files = input_api.AffectedFiles(include_deletes=False)
  for f in files:
    path = f.LocalPath()
    root, ext = input_api.os_path.splitext(path)
    # Look for unsupported extensions.
    if not ext in SOURCE_FILE_EXTENSIONS:
      continue
    # Look for excluded paths.
    found = False
    for item in excluded_paths:
      if item.match(path):
        found = True
        break
    if found:
      continue

    # Need to read the file ourselves since AffectedFile.NewContents()
    # will normalize line endings.
    contents = input_api.ReadFile(f, 'rb')
    if '\r' in contents:
      cr_files.append(path)

    # Check that the file ends in one and only one newline character.
    if len(contents) > 0 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"):
      eof_files.append(path)

    local_errors = []
    # Remove end of line character.
    lines = contents.splitlines()
    line_num = 1
    for line in lines:
      if line.endswith(' '):
        local_errors.append(output_api.PresubmitError(
            '%s, line %s ends with whitespaces.' %
            (path, line_num)))
      # Accept lines with http://, https:// and C #define/#pragma/#include to
      # exceed the max_cols rule.
      if (max_cols and
          len(line) > max_cols and
          not 'http://' in line and
          not 'https://' in line and
          not (line[0] == '#' and ext in C_SOURCE_FILE_EXTENSIONS)):
        local_errors.append(output_api.PresubmitError(
            '%s, line %s has %s chars, please reduce to %d chars.' %
            (path, line_num, len(line), max_cols)))
      if '\t' in line:
        local_errors.append(output_api.PresubmitError(
            "%s, line %s contains a tab character." %
            (path, line_num)))
      line_num += 1
      # Just show the first 5 errors.
      if len(local_errors) == 6:
        local_errors.pop()
        local_errors.append(output_api.PresubmitError("... and more."))
        break
    results.extend(local_errors)

  if cr_files:
    results.append(output_api.PresubmitError(
        'Found CR (or CRLF) line ending in these files, please use only LF:',
        items=cr_files))
  if eof_files:
    results.append(output_api.PresubmitError(
        'These files should end in one (and only one) newline character:',
        items=eof_files))
  return results

# Copyright (c) 2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import re
import subprocess
import os

try:
  from internal import internal_presubmit
except ImportError:
  internal_presubmit = None


SOURCE_FILE_EXTENSIONS = [
        '.c', '.cc', '.cpp', '.h', '.m', '.mm', '.py', '.mk', '.am', '.json',
        '.gyp', '.gypi'
        ]

EXCLUDED_PATHS = []

# Finds what seem to be definitions of DllRegisterServer.
DLL_REGISTER_SERVER_RE = re.compile('\s*STDAPI\s+DllRegisterServer\s*\(')

# Matches a Tracker story URL
story_url_re = re.compile('https?://tracker.+/[0-9]+')

# Matches filenames in which we allow tabs
tabs_ok_re = re.compile('.*\.(vcproj|vsprops|sln)$')

# Matches filenames of source files
source_files_re = re.compile('.*\.(cc|h|py|js)$')


# The top-level source directory.
_SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))


def CheckChange(input_api, output_api, committing, is_chrome_frame=False):
  results = []

  do_not_submit_errors = input_api.canned_checks.CheckDoNotSubmit(input_api,
                                                                  output_api)
  if committing:
    results += do_not_submit_errors
  elif do_not_submit_errors:
    results += [output_api.PresubmitNotifyResult(
        'There is a DO-NOT-SUBMIT issue')]

  results += CheckChangeHasNoTabs(input_api, output_api)
  results += CheckLongLines(input_api, output_api)
  results += CheckHasStoryOrBug(input_api, output_api)
  results += LocalChecks(input_api, output_api)
  results += WarnOnAtlSmartPointers(input_api, output_api)
  if not is_chrome_frame:
    results += CheckNoDllRegisterServer(input_api, output_api)
    results += CheckUnittestsRan(input_api, output_api, committing)
    if internal_presubmit:
      results += internal_presubmit.InternalChecks(input_api, output_api,
                                                   committing)
  return results


def CheckHasStoryOrBug(input_api, output_api):
  """We require either BUG= to be present and non-empty.  For
  a completely trivial change, use BUG=none.
  """
  if not ('BUG' in input_api.change.tags):
    return [output_api.PresubmitError('A BUG= tag is required. For trivial '
                                      'changes you can use BUG=none.')]
  if ('BUG' in input_api.change.tags and
      len(input_api.change.tags['BUG']) == 0):
    return [output_api.PresubmitError('A non-empty BUG= is required. For '
                                      'trivial changes you can use BUG=none.')]
  return []


def CheckChangeHasNoTabs(input_api, output_api):
  """Slightly modified version of the canned check with the same name.

  This version ignores certain file types in which we allow tabs.
  """
  for f, line_num, line in input_api.RightHandSideLines():
    if tabs_ok_re.match(f.LocalPath()):
      continue
    if '\t' in line:
      return [output_api.PresubmitError(
          "Found a tab character in %s, line %s" %
          (f.LocalPath(), line_num))]
  return []


def CheckLongLines(input_api, output_api, maxlen=80):
  """Checks that there aren't any lines longer than maxlen characters in any of
  the text files to be submitted.
  """
  basename = input_api.basename

  bad = []
  for f, line_num, line in input_api.RightHandSideLines():
    if not source_files_re.match(f.LocalPath()):
      continue
    if line.find('http://') != -1:
      # Exemption for long URLs
      continue
    if line.endswith('\n'):
      line = line[:-1]
    if len(line) > maxlen:
      bad.append(
          '%s, line %s, %s chars' %
          (basename(f.LocalPath()), line_num, len(line)))
      if len(bad) == 5:  # Just show the first 5 errors.
        break

  if bad:
    msg = "Found lines longer than %s characters (first 5 shown)." % maxlen
    return [output_api.PresubmitPromptWarning(msg, items=bad)]
  else:
    return []


_UNITTEST_MESSAGE = '''\
You must build and run the CEEE smoke tests before submitting. To clear this
error, run the script "smoke_test.bat" in the CEEE directory.
'''


def CheckUnittestsRan(input_api, output_api, committing):
  '''Checks that the unittests success file is newer than any modified file'''
  # But only if there were IE files modified, since we only have unit tests
  # for CEEE IE.
  files = []
  ie_paths_re = re.compile('ceee[\\\\/](ie|common)[\\\\/]')
  for f in input_api.AffectedFiles(include_deletes = False):
    path = f.LocalPath()
    if (ie_paths_re.match(path)):
      files.append(f)

  if not files:
    return []

  def MakeResult(message, modified_files=[]):
    if committing:
      return output_api.PresubmitError(message, modified_files)
    else:
      return output_api.PresubmitNotifyResult(message, modified_files)
  os_path = input_api.os_path

  success_files = [
      os_path.join(input_api.PresubmitLocalPath(),
                    '../chrome/Debug/ceee.success'),
      os_path.join(input_api.PresubmitLocalPath(),
                   '../chrome/Release/ceee.success')]

  if (not os_path.exists(success_files[0]) or
      not os_path.exists(success_files[1])):
    return [MakeResult(_UNITTEST_MESSAGE)]

  success_time = min(os.stat(success_files[0]).st_mtime,
                     os.stat(success_files[1]).st_mtime)
  modified_files = []
  for f in modified_files:
    file_time = os.stat(f.AbsoluteLocalPath()).st_mtime
    if file_time > success_time:
      modified_files.append(f.LocalPath())

  result = []
  if modified_files:
    result.append(MakeResult('These files have been modified since Debug and/or'
                             ' Release unittests were built.', modified_files))

  return result


def CheckNoDllRegisterServer(input_api, output_api):
  for f, line_num, line in input_api.RightHandSideLines():
    if DLL_REGISTER_SERVER_RE.search(line):
      file_name = os.path.basename(f.LocalPath())
      if file_name not in ['install_utils.h', 'install_utils_unittest.cc']:
        return [output_api.PresubmitError(
            '%s contains a definition of DllRegisterServer at line %s.\n'
            'Please search for CEEE_DEFINE_DLL_REGISTER_SERVER.' %
            (f.LocalPath(), line_num))]
  return []


def WarnOnAtlSmartPointers(input_api, output_api):
  smart_pointer_re = re.compile(r'\bCCom(Ptr|BSTR|Variant)\b')
  bad_files = []
  for f in input_api.AffectedFiles(include_deletes=False):
    contents = input_api.ReadFile(f, 'r')
    if smart_pointer_re.search(contents):
      bad_files.append(f.LocalPath())
  if bad_files:
    return [output_api.PresubmitPromptWarning(
        'The following files use CComPtr, CComBSTR and/or CComVariant.\n'
        'Please consider switching to ScopedComPtr, ScopedBstr and/or\n'
        'ScopedVariant as per team policy. (NOTE: Will soon be an error.)',
        items=bad_files)]
  else:
    return []


def LocalChecks(input_api, output_api, max_cols=80):
  """Reports an error if for any source file in SOURCE_FILE_EXTENSIONS:
     - uses CR (or CRLF)
     - contains a TAB
     - has a line that ends with whitespace
     - contains a line >|max_cols| cols unless |max_cols| is 0.
     - File does not end in a newline, or ends in more than one.

  Note that the whole file is checked, not only the changes.
  """
  C_SOURCE_FILE_EXTENSIONS = ('.c', '.cc', '.cpp', '.h', '.inl')
  cr_files = []
  eof_files = []
  results = []
  excluded_paths = [input_api.re.compile(x) for x in EXCLUDED_PATHS]
  files = input_api.AffectedFiles(include_deletes=False)
  for f in files:
    path = f.LocalPath()
    root, ext = input_api.os_path.splitext(path)
    # Look for unsupported extensions.
    if not ext in SOURCE_FILE_EXTENSIONS:
      continue
    # Look for excluded paths.
    found = False
    for item in excluded_paths:
      if item.match(path):
        found = True
        break
    if found:
      continue

    # Need to read the file ourselves since AffectedFile.NewContents()
    # will normalize line endings.
    contents = input_api.ReadFile(f, 'rb')
    if '\r' in contents:
      cr_files.append(path)

    # Check that the file ends in one and only one newline character.
    if len(contents) > 0 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"):
      eof_files.append(path)

    local_errors = []
    # Remove end of line character.
    lines = contents.splitlines()
    line_num = 1
    for line in lines:
      if line.endswith(' '):
        local_errors.append(output_api.PresubmitError(
            '%s, line %s ends with whitespaces.' %
            (path, line_num)))
      # Accept lines with http://, https:// and C #define/#pragma/#include to
      # exceed the max_cols rule.
      if (max_cols and
          len(line) > max_cols and
          not 'http://' in line and
          not 'https://' in line and
          not (line[0] == '#' and ext in C_SOURCE_FILE_EXTENSIONS)):
        local_errors.append(output_api.PresubmitError(
            '%s, line %s has %s chars, please reduce to %d chars.' %
            (path, line_num, len(line), max_cols)))
      if '\t' in line:
        local_errors.append(output_api.PresubmitError(
            "%s, line %s contains a tab character." %
            (path, line_num)))
      line_num += 1
      # Just show the first 5 errors.
      if len(local_errors) == 6:
        local_errors.pop()
        local_errors.append(output_api.PresubmitError("... and more."))
        break
    results.extend(local_errors)

  if cr_files:
    results.append(output_api.PresubmitError(
        'Found CR (or CRLF) line ending in these files, please use only LF:',
        items=cr_files))
  if eof_files:
    results.append(output_api.PresubmitError(
        'These files should end in one (and only one) newline character:',
        items=eof_files))
  return results

# Copyright (c) 2010 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Finds what seem to be definitions of DllRegisterServer. # Matches a Tracker story URL # Matches filenames in which we allow tabs # Matches filenames of source files # The top-level source directory. We require either BUG= to be present and non-empty.  For   a completely trivial change, use BUG=none. Slightly modified version of the canned check with the same name.    This version ignores certain file types in which we allow tabs. Checks that there aren't any lines longer than maxlen characters in any of   the text files to be submitted. # Exemption for long URLs # Just show the first 5 errors. \ You must build and run the CEEE smoke tests before submitting. To clear this error, run the script "smoke_test.bat" in the CEEE directory. Checks that the unittests success file is newer than any modified file # But only if there were IE files modified, since we only have unit tests # for CEEE IE. Reports an error if for any source file in SOURCE_FILE_EXTENSIONS:      - uses CR (or CRLF)      - contains a TAB      - has a line that ends with whitespace      - contains a line >|max_cols| cols unless |max_cols| is 0.      - File does not end in a newline, or ends in more than one.    Note that the whole file is checked, not only the changes. # Look for unsupported extensions. # Look for excluded paths. # Need to read the file ourselves since AffectedFile.NewContents() # will normalize line endings. # Check that the file ends in one and only one newline character. # Remove end of line character. # Accept lines with http://, https:// and C #define/#pragma/#include to # exceed the max_cols rule. # Just show the first 5 errors.