Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

misc.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2006, 2009-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  3. # Copyright (c) 2012-2014 Google, Inc.
  4. # Copyright (c) 2014-2017 Claudiu Popa <pcmanticore@gmail.com>
  5. # Copyright (c) 2014 Brett Cannon <brett@python.org>
  6. # Copyright (c) 2014 Alexandru Coman <fcoman@bitdefender.com>
  7. # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
  8. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  9. # Copyright (c) 2016 Łukasz Rogalski <rogalski.91@gmail.com>
  10. # Copyright (c) 2016 glegoux <gilles.legoux@gmail.com>
  11. # Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
  12. # Copyright (c) 2017 Mikhail Fesenko <proggga@gmail.com>
  13. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  14. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  15. """Check source code is ascii only or has an encoding declaration (PEP 263)"""
  16. # pylint: disable=W0511
  17. import re
  18. import six
  19. from pylint.interfaces import IRawChecker
  20. from pylint.checkers import BaseChecker
  21. from pylint.utils import OPTION_RGX
  22. MSGS = {
  23. 'W0511': ('%s',
  24. 'fixme',
  25. 'Used when a warning note as FIXME or XXX is detected.'),
  26. 'W0512': ('Cannot decode using encoding "%s",'
  27. ' unexpected byte at position %d',
  28. 'invalid-encoded-data',
  29. 'Used when a source line cannot be decoded using the specified '
  30. 'source file encoding.',
  31. {'maxversion': (3, 0)}),
  32. }
  33. class EncodingChecker(BaseChecker):
  34. """checks for:
  35. * warning notes in the code like FIXME, XXX
  36. * encoding issues.
  37. """
  38. __implements__ = IRawChecker
  39. # configuration section name
  40. name = 'miscellaneous'
  41. msgs = MSGS
  42. options = (('notes',
  43. {'type': 'csv', 'metavar': '<comma separated values>',
  44. 'default': ('FIXME', 'XXX', 'TODO'),
  45. 'help': ('List of note tags to take in consideration, '
  46. 'separated by a comma.')}),)
  47. def _check_note(self, notes, lineno, line, module_last_lineno):
  48. """
  49. Add the message 'fixme' in case a note is found in the line.
  50. :param notes: regular expression object matching any notes
  51. (XXX, TODO, FIXME) behind a '#'
  52. :type notes: re.pattern object
  53. :param lineno: line number
  54. :type lineno: int
  55. :param line: line to be checked
  56. :type line: str
  57. :param module_last_lineno: last line number of the module as parsed by astroid
  58. (may be different from real last line number in case
  59. commented lines exist at the end of the module)
  60. :type module_last_lineno: int
  61. """
  62. # First, simply check if the notes are in the line at all. This is an
  63. # optimisation to prevent using the regular expression on every line,
  64. # but rather only on lines which may actually contain one of the notes.
  65. # This prevents a pathological problem with lines that are hundreds
  66. # of thousands of characters long.
  67. for note in self.config.notes:
  68. if note in line:
  69. break
  70. else:
  71. return
  72. match = notes.search(line)
  73. if not match:
  74. return
  75. # In case the module ends with commented lines, the astroid parser
  76. # don't take into account those lines, then:
  77. # - the line number of those lines is greater than the
  78. # module last line number (module.tolineno)
  79. # - astroid module object can't inform pylint
  80. # of disabled messages in those extra lines.
  81. if lineno > module_last_lineno:
  82. disable_option_match = OPTION_RGX.search(line)
  83. if disable_option_match:
  84. try:
  85. _, value = disable_option_match.group(1).split('=', 1)
  86. values = [_val.strip().upper() for _val in value.split(',')]
  87. if set(values) & set(self.config.notes):
  88. return
  89. except ValueError:
  90. self.add_message('bad-inline-option',
  91. args=disable_option_match.group(1).strip(), line=line)
  92. return
  93. self.add_message('fixme', args=line[match.start(1):].rstrip(), line=lineno)
  94. def _check_encoding(self, lineno, line, file_encoding):
  95. try:
  96. return six.text_type(line, file_encoding)
  97. except UnicodeDecodeError as ex:
  98. self.add_message('invalid-encoded-data', line=lineno,
  99. args=(file_encoding, ex.args[2]))
  100. except LookupError as ex:
  101. if (line.startswith('#') and
  102. "coding" in line and file_encoding in line):
  103. self.add_message('syntax-error',
  104. line=lineno,
  105. args='Cannot decode using encoding "{}",'
  106. ' bad encoding'.format(file_encoding))
  107. def process_module(self, module):
  108. """inspect the source file to find encoding problem or fixmes like
  109. notes
  110. """
  111. if self.config.notes:
  112. notes = re.compile(
  113. r'.*?#\s*(%s)(:*\s*.*)' % "|".join(self.config.notes))
  114. else:
  115. notes = None
  116. if module.file_encoding:
  117. encoding = module.file_encoding
  118. else:
  119. encoding = 'ascii'
  120. with module.stream() as stream:
  121. for lineno, line in enumerate(stream):
  122. line = self._check_encoding(lineno + 1, line, encoding)
  123. if line is not None and notes:
  124. self._check_note(notes, lineno + 1, line, module.tolineno)
  125. def register(linter):
  126. """required method to auto register this checker"""
  127. linter.register_checker(EncodingChecker(linter))