__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
  2. # http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. #
  4. # This program is free software; you can redistribute it and/or modify it under
  5. # the terms of the GNU General Public License as published by the Free Software
  6. # Foundation; either version 2 of the License, or (at your option) any later
  7. # version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License along with
  14. # this program; if not, write to the Free Software Foundation, Inc.,
  15. # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  16. """utilities methods and classes for checkers
  17. Base id of standard checkers (used in msg and report ids):
  18. 01: base
  19. 02: classes
  20. 03: format
  21. 04: import
  22. 05: misc
  23. 06: variables
  24. 07: exceptions
  25. 08: similar
  26. 09: design_analysis
  27. 10: newstyle
  28. 11: typecheck
  29. 12: logging
  30. 13: string_format
  31. 14-50: not yet used: reserved for future internal checkers.
  32. 51-99: perhaps used: reserved for external checkers
  33. The raw_metrics checker has no number associated since it doesn't emit any
  34. messages nor reports. XXX not true, emit a 07 report !
  35. """
  36. import tokenize
  37. from os import listdir
  38. from os.path import dirname, join, isdir, splitext
  39. from logilab.astng.utils import ASTWalker
  40. from logilab.common.configuration import OptionsProviderMixIn
  41. from pylint.reporters import diff_string, EmptyReport
  42. def table_lines_from_stats(stats, old_stats, columns):
  43. """get values listed in <columns> from <stats> and <old_stats>,
  44. and return a formated list of values, designed to be given to a
  45. ureport.Table object
  46. """
  47. lines = []
  48. for m_type in columns:
  49. new = stats[m_type]
  50. format = str
  51. if isinstance(new, float):
  52. format = lambda num: '%.3f' % num
  53. old = old_stats.get(m_type)
  54. if old is not None:
  55. diff_str = diff_string(old, new)
  56. old = format(old)
  57. else:
  58. old, diff_str = 'NC', 'NC'
  59. lines += (m_type.replace('_', ' '), format(new), old, diff_str)
  60. return lines
  61. class BaseChecker(OptionsProviderMixIn, ASTWalker):
  62. """base class for checkers"""
  63. # checker name (you may reuse an existing one)
  64. name = None
  65. # options level (0 will be displaying in --help, 1 in --long-help)
  66. level = 1
  67. # ordered list of options to control the ckecker behaviour
  68. options = ()
  69. # messages issued by this checker
  70. msgs = {}
  71. # reports issued by this checker
  72. reports = ()
  73. def __init__(self, linter=None):
  74. """checker instances should have the linter as argument
  75. linter is an object implementing ILinter
  76. """
  77. ASTWalker.__init__(self, self)
  78. self.name = self.name.lower()
  79. OptionsProviderMixIn.__init__(self)
  80. self.linter = linter
  81. # messages that are active for the current check
  82. self.active_msgs = set()
  83. def add_message(self, msg_id, line=None, node=None, args=None):
  84. """add a message of a given type"""
  85. self.linter.add_message(msg_id, line, node, args)
  86. def package_dir(self):
  87. """return the base directory for the analysed package"""
  88. return dirname(self.linter.base_file)
  89. # dummy methods implementing the IChecker interface
  90. def open(self):
  91. """called before visiting project (i.e set of modules)"""
  92. def close(self):
  93. """called after visiting project (i.e set of modules)"""
  94. class BaseRawChecker(BaseChecker):
  95. """base class for raw checkers"""
  96. def process_module(self, node):
  97. """process a module
  98. the module's content is accessible via the stream object
  99. stream must implement the readline method
  100. """
  101. stream = node.file_stream
  102. stream.seek(0) # XXX may be removed with astng > 0.23
  103. self.process_tokens(tokenize.generate_tokens(stream.readline))
  104. def process_tokens(self, tokens):
  105. """should be overridden by subclasses"""
  106. raise NotImplementedError()
  107. PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll')
  108. def initialize(linter):
  109. """initialize linter with checkers in this package """
  110. package_load(linter, __path__[0])
  111. def package_load(linter, directory):
  112. """load all module and package in the given directory, looking for a
  113. 'register' function in each one, used to register pylint checkers
  114. """
  115. globs = globals()
  116. imported = {}
  117. for filename in listdir(directory):
  118. basename, extension = splitext(filename)
  119. if basename in imported or basename == '__pycache__':
  120. continue
  121. if extension in PY_EXTS and basename != '__init__' or (
  122. not extension and basename != 'CVS' and
  123. isdir(join(directory, basename))):
  124. try:
  125. module = __import__(basename, globs, globs, None)
  126. except ValueError:
  127. # empty module name (usually emacs auto-save files)
  128. continue
  129. except ImportError, exc:
  130. import sys
  131. print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc)
  132. else:
  133. if hasattr(module, 'register'):
  134. module.register(linter)
  135. imported[basename] = 1
  136. __all__ = ('CheckerHandler', 'BaseChecker', 'initialize', 'package_load')