python.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. from typing import List
  2. from openhands.core.logger import openhands_logger as logger
  3. from openhands.linter.base import BaseLinter, LintResult
  4. from openhands.linter.utils import run_cmd
  5. def python_compile_lint(fname: str) -> list[LintResult]:
  6. try:
  7. with open(fname, 'r') as f:
  8. code = f.read()
  9. compile(code, fname, 'exec') # USE TRACEBACK BELOW HERE
  10. return []
  11. except SyntaxError as err:
  12. err_lineno = getattr(err, 'end_lineno', err.lineno)
  13. err_offset = getattr(err, 'end_offset', err.offset)
  14. if err_offset and err_offset < 0:
  15. err_offset = err.offset
  16. return [
  17. LintResult(
  18. file=fname, line=err_lineno, column=err_offset or 1, message=err.msg
  19. )
  20. ]
  21. def flake_lint(filepath: str) -> list[LintResult]:
  22. fatal = 'F821,F822,F831,E112,E113,E999,E902'
  23. flake8_cmd = f'flake8 --select={fatal} --isolated {filepath}'
  24. try:
  25. cmd_outputs = run_cmd(flake8_cmd)
  26. except FileNotFoundError:
  27. return []
  28. results: list[LintResult] = []
  29. if not cmd_outputs:
  30. return results
  31. for line in cmd_outputs.splitlines():
  32. parts = line.split(':')
  33. if len(parts) >= 4:
  34. _msg = parts[3].strip()
  35. if len(parts) > 4:
  36. _msg += ': ' + parts[4].strip()
  37. try:
  38. line_num = int(parts[1])
  39. except ValueError as e:
  40. logger.warning(
  41. f'Error parsing flake8 output for line: {e}. Parsed parts: {parts}. Skipping...'
  42. )
  43. continue
  44. try:
  45. column_num = int(parts[2])
  46. except ValueError as e:
  47. column_num = 1
  48. _msg = (
  49. parts[2].strip() + ' ' + _msg
  50. ) # add the unparsed message to the original message
  51. logger.warning(
  52. f'Error parsing flake8 output for column: {e}. Parsed parts: {parts}. Using default column 1.'
  53. )
  54. results.append(
  55. LintResult(
  56. file=filepath,
  57. line=line_num,
  58. column=column_num,
  59. message=_msg,
  60. )
  61. )
  62. return results
  63. class PythonLinter(BaseLinter):
  64. @property
  65. def supported_extensions(self) -> List[str]:
  66. return ['.py']
  67. def lint(self, file_path: str) -> list[LintResult]:
  68. error = flake_lint(file_path)
  69. if not error:
  70. error = python_compile_lint(file_path)
  71. return error
  72. def compile_lint(self, file_path: str, code: str) -> List[LintResult]:
  73. try:
  74. compile(code, file_path, 'exec')
  75. return []
  76. except SyntaxError as e:
  77. return [
  78. LintResult(
  79. file=file_path,
  80. line=e.lineno,
  81. column=e.offset,
  82. message=str(e),
  83. rule='SyntaxError',
  84. )
  85. ]