utils.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  3. import contextlib
  4. import sys
  5. import traceback
  6. from datetime import datetime
  7. from pathlib import Path
  8. from pylint.config import PYLINT_HOME
  9. from pylint.lint.expand_modules import get_python_path
  10. class ArgumentPreprocessingError(Exception):
  11. """Raised if an error occurs during argument preprocessing."""
  12. def prepare_crash_report(ex: Exception, filepath: str, crash_file_path: str) -> Path:
  13. issue_template_path = (
  14. Path(PYLINT_HOME) / datetime.now().strftime(str(crash_file_path))
  15. ).resolve()
  16. with open(filepath, encoding="utf8") as f:
  17. file_content = f.read()
  18. template = ""
  19. if not issue_template_path.exists():
  20. template = """\
  21. First, please verify that the bug is not already filled:
  22. https://github.com/PyCQA/pylint/issues/
  23. Then create a new crash issue:
  24. https://github.com/PyCQA/pylint/issues/new?assignees=&labels=crash%2Cneeds+triage&template=BUG-REPORT.yml
  25. """
  26. template += f"""\
  27. Issue title:
  28. Crash ``{ex}`` (if possible, be more specific about what made pylint crash)
  29. Content:
  30. When parsing the following file:
  31. <!--
  32. If sharing the code is not an option, please state so,
  33. but providing only the stacktrace would still be helpful.
  34. -->
  35. ```python
  36. {file_content}
  37. ```
  38. pylint crashed with a ``{ex.__class__.__name__}`` and with the following stacktrace:
  39. ```
  40. """
  41. try:
  42. with open(issue_template_path, "a", encoding="utf8") as f:
  43. f.write(template)
  44. traceback.print_exc(file=f)
  45. f.write("```\n")
  46. except FileNotFoundError:
  47. print(f"Can't write the issue template for the crash in {issue_template_path}.")
  48. return issue_template_path
  49. def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str:
  50. return (
  51. f"Fatal error while checking '{filepath}'. "
  52. f"Please open an issue in our bug tracker so we address this. "
  53. f"There is a pre-filled template that you can use in '{issue_template_path}'."
  54. )
  55. def preprocess_options(args, search_for):
  56. """look for some options (keys of <search_for>) which have to be processed
  57. before others
  58. values of <search_for> are callback functions to call when the option is
  59. found
  60. """
  61. i = 0
  62. while i < len(args):
  63. arg = args[i]
  64. if not arg.startswith("--"):
  65. i += 1
  66. else:
  67. try:
  68. option, val = arg[2:].split("=", 1)
  69. except ValueError:
  70. option, val = arg[2:], None
  71. try:
  72. cb, takearg = search_for[option]
  73. except KeyError:
  74. i += 1
  75. else:
  76. del args[i]
  77. if takearg and val is None:
  78. if i >= len(args) or args[i].startswith("-"):
  79. msg = f"Option {option} expects a value"
  80. raise ArgumentPreprocessingError(msg)
  81. val = args[i]
  82. del args[i]
  83. elif not takearg and val is not None:
  84. msg = f"Option {option} doesn't expects a value"
  85. raise ArgumentPreprocessingError(msg)
  86. cb(option, val)
  87. def _patch_sys_path(args):
  88. original = list(sys.path)
  89. changes = []
  90. seen = set()
  91. for arg in args:
  92. path = get_python_path(arg)
  93. if path not in seen:
  94. changes.append(path)
  95. seen.add(path)
  96. sys.path[:] = changes + sys.path
  97. return original
  98. @contextlib.contextmanager
  99. def fix_import_path(args):
  100. """Prepare sys.path for running the linter checks.
  101. Within this context, each of the given arguments is importable.
  102. Paths are added to sys.path in corresponding order to the arguments.
  103. We avoid adding duplicate directories to sys.path.
  104. `sys.path` is reset to its original value upon exiting this context.
  105. """
  106. original = _patch_sys_path(args)
  107. try:
  108. yield
  109. finally:
  110. sys.path[:] = original