# -*- coding: utf-8 -*- # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING from __future__ import print_function import codecs import re import sys import tokenize from os import linesep, listdir from os.path import basename, dirname, exists, isdir, join, normpath, splitext from astroid import Module, modutils from pylint.constants import MSG_TYPES, MSG_TYPES_LONG, PY_EXTS from pylint.utils.normalize_text import normalize_text def get_module_and_frameid(node): """return the module name and the frame id in the module""" frame = node.frame() module, obj = "", [] while frame: if isinstance(frame, Module): module = frame.name else: obj.append(getattr(frame, "name", "")) try: frame = frame.parent.frame() except AttributeError: frame = None obj.reverse() return module, ".".join(obj) def category_id(cid): cid = cid.upper() if cid in MSG_TYPES: return cid return MSG_TYPES_LONG.get(cid) def safe_decode(line, encoding, *args, **kwargs): """return decoded line from encoding or decode with default encoding""" try: return line.decode(encoding or sys.getdefaultencoding(), *args, **kwargs) except LookupError: return line.decode(sys.getdefaultencoding(), *args, **kwargs) def decoding_stream(stream, encoding, errors="strict"): try: reader_cls = codecs.getreader(encoding or sys.getdefaultencoding()) except LookupError: reader_cls = codecs.getreader(sys.getdefaultencoding()) return reader_cls(stream, errors) def tokenize_module(module): with module.stream() as stream: readline = stream.readline return list(tokenize.tokenize(readline)) def _basename_in_blacklist_re(base_name, black_list_re): """Determines if the basename is matched in a regex blacklist :param str base_name: The basename of the file :param list black_list_re: A collection of regex patterns to match against. Successful matches are blacklisted. :returns: `True` if the basename is blacklisted, `False` otherwise. :rtype: bool """ for file_pattern in black_list_re: if file_pattern.match(base_name): return True return False def _modpath_from_file(filename, is_namespace): def _is_package_cb(path, parts): return modutils.check_modpath_has_init(path, parts) or is_namespace return modutils.modpath_from_file_with_callback( filename, is_package_cb=_is_package_cb ) def expand_modules(files_or_modules, black_list, black_list_re): """take a list of files/modules/packages and return the list of tuple (file, module name) which have to be actually checked """ result = [] errors = [] for something in files_or_modules: if basename(something) in black_list: continue if _basename_in_blacklist_re(basename(something), black_list_re): continue if exists(something): # this is a file or a directory try: modname = ".".join(modutils.modpath_from_file(something)) except ImportError: modname = splitext(basename(something))[0] if isdir(something): filepath = join(something, "__init__.py") else: filepath = something else: # suppose it's a module or package modname = something try: filepath = modutils.file_from_modpath(modname.split(".")) if filepath is None: continue except (ImportError, SyntaxError) as ex: # FIXME p3k : the SyntaxError is a Python bug and should be # removed as soon as possible http://bugs.python.org/issue10588 errors.append({"key": "fatal", "mod": modname, "ex": ex}) continue filepath = normpath(filepath) modparts = (modname or something).split(".") try: spec = modutils.file_info_from_modpath(modparts, path=sys.path) except ImportError: # Might not be acceptable, don't crash. is_namespace = False is_directory = isdir(something) else: is_namespace = modutils.is_namespace(spec) is_directory = modutils.is_directory(spec) if not is_namespace: result.append( { "path": filepath, "name": modname, "isarg": True, "basepath": filepath, "basename": modname, } ) has_init = ( not (modname.endswith(".__init__") or modname == "__init__") and basename(filepath) == "__init__.py" ) if has_init or is_namespace or is_directory: for subfilepath in modutils.get_module_files( dirname(filepath), black_list, list_all=is_namespace ): if filepath == subfilepath: continue if _basename_in_blacklist_re(basename(subfilepath), black_list_re): continue modpath = _modpath_from_file(subfilepath, is_namespace) submodname = ".".join(modpath) result.append( { "path": subfilepath, "name": submodname, "isarg": False, "basepath": filepath, "basename": modname, } ) return result, errors def register_plugins(linter, directory): """load all module and package in the given directory, looking for a 'register' function in each one, used to register pylint checkers """ imported = {} for filename in listdir(directory): base, extension = splitext(filename) if base in imported or base == "__pycache__": continue if ( extension in PY_EXTS and base != "__init__" or (not extension and isdir(join(directory, base))) ): try: module = modutils.load_module_from_file(join(directory, filename)) except ValueError: # empty module name (usually emacs auto-save files) continue except ImportError as exc: print( "Problem importing module %s: %s" % (filename, exc), file=sys.stderr ) else: if hasattr(module, "register"): module.register(linter) imported[base] = 1 def get_global_option(checker, option, default=None): """ Retrieve an option defined by the given *checker* or by all known option providers. It will look in the list of all options providers until the given *option* will be found. If the option wasn't found, the *default* value will be returned. """ # First, try in the given checker's config. # After that, look in the options providers. try: return getattr(checker.config, option.replace("-", "_")) except AttributeError: pass for provider in checker.linter.options_providers: for options in provider.options: if options[0] == option: return getattr(provider.config, option.replace("-", "_")) return default def deprecated_option( shortname=None, opt_type=None, help_msg=None, deprecation_msg=None ): def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument if deprecation_msg: sys.stderr.write(deprecation_msg % (optname,)) option = { "help": help_msg, "hide": True, "type": opt_type, "action": "callback", "callback": _warn_deprecated, "deprecated": True, } if shortname: option["shortname"] = shortname return option def _splitstrip(string, sep=","): """return a list of stripped string by splitting the string given as argument on `sep` (',' by default). Empty string are discarded. >>> _splitstrip('a, b, c , 4,,') ['a', 'b', 'c', '4'] >>> _splitstrip('a') ['a'] >>> _splitstrip('a,\nb,\nc,') ['a', 'b', 'c'] :type string: str or unicode :param string: a csv line :type sep: str or unicode :param sep: field separator, default to the comma (',') :rtype: str or unicode :return: the unquoted string (or the input string if it wasn't quoted) """ return [word.strip() for word in string.split(sep) if word.strip()] def _unquote(string): """remove optional quotes (simple or double) from the string :type string: str or unicode :param string: an optionally quoted string :rtype: str or unicode :return: the unquoted string (or the input string if it wasn't quoted) """ if not string: return string if string[0] in "\"'": string = string[1:] if string[-1] in "\"'": string = string[:-1] return string def _check_csv(value): if isinstance(value, (list, tuple)): return value return _splitstrip(value) def _comment(string): """return string as a comment""" lines = [line.strip() for line in string.splitlines()] return "# " + ("%s# " % linesep).join(lines) def _format_option_value(optdict, value): """return the user input's value from a 'compiled' value""" if isinstance(value, (list, tuple)): value = ",".join(_format_option_value(optdict, item) for item in value) elif isinstance(value, dict): value = ",".join("%s:%s" % (k, v) for k, v in value.items()) elif hasattr(value, "match"): # optdict.get('type') == 'regexp' # compiled regexp value = value.pattern elif optdict.get("type") == "yn": value = "yes" if value else "no" elif isinstance(value, str) and value.isspace(): value = "'%s'" % value return value def format_section(stream, section, options, doc=None): """format an options section using the INI format""" if doc: print(_comment(doc), file=stream) print("[%s]" % section, file=stream) _ini_format(stream, options) def _ini_format(stream, options): """format options using the INI format""" for optname, optdict, value in options: value = _format_option_value(optdict, value) help_opt = optdict.get("help") if help_opt: help_opt = normalize_text(help_opt, line_len=79, indent="# ") print(file=stream) print(help_opt, file=stream) else: print(file=stream) if value is None: print("#%s=" % optname, file=stream) else: value = str(value).strip() if re.match(r"^([\w-]+,)+[\w-]+$", str(value)): separator = "\n " + " " * len(optname) value = separator.join(x + "," for x in str(value).split(",")) # remove trailing ',' from last element of the list value = value[:-1] print("%s=%s" % (optname, value), file=stream)