xref: /aosp_15_r20/external/yapf/yapf/yapflib/verifier.py (revision 7249d1a64f4850ccf838e62a46276f891f72998e)
1# Copyright 2015 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Verify that the generated code is valid code.
15
16This takes a line of code and "normalizes" it. I.e., it transforms the snippet
17into something that has the potential to compile.
18
19    VerifyCode(): the main function exported by this module.
20"""
21
22import ast
23import re
24import sys
25import textwrap
26
27
28class InternalError(Exception):
29  """Internal error in verifying formatted code."""
30  pass
31
32
33def VerifyCode(code):
34  """Verify that the reformatted code is syntactically correct.
35
36  Arguments:
37    code: (unicode) The reformatted code snippet.
38
39  Raises:
40    SyntaxError if the code was reformatted incorrectly.
41  """
42  try:
43    compile(textwrap.dedent(code).encode('UTF-8'), '<string>', 'exec')
44  except SyntaxError:
45    try:
46      ast.parse(textwrap.dedent(code.lstrip('\n')).lstrip(), '<string>', 'exec')
47    except SyntaxError:
48      try:
49        normalized_code = _NormalizeCode(code)
50        compile(normalized_code.encode('UTF-8'), '<string>', 'exec')
51      except SyntaxError:
52        raise InternalError(sys.exc_info()[1])
53
54
55def _NormalizeCode(code):
56  """Make sure that the code snippet is compilable."""
57  code = textwrap.dedent(code.lstrip('\n')).lstrip()
58
59  # Split the code to lines and get rid of all leading full-comment lines as
60  # they can mess up the normalization attempt.
61  lines = code.split('\n')
62  i = 0
63  for i, line in enumerate(lines):
64    line = line.strip()
65    if line and not line.startswith('#'):
66      break
67  code = '\n'.join(lines[i:]) + '\n'
68
69  if re.match(r'(if|while|for|with|def|class|async|await)\b', code):
70    code += '\n    pass'
71  elif re.match(r'(elif|else)\b', code):
72    try:
73      try_code = 'if True:\n    pass\n' + code + '\n    pass'
74      ast.parse(
75          textwrap.dedent(try_code.lstrip('\n')).lstrip(), '<string>', 'exec')
76      code = try_code
77    except SyntaxError:
78      # The assumption here is that the code is on a single line.
79      code = 'if True: pass\n' + code
80  elif code.startswith('@'):
81    code += '\ndef _():\n    pass'
82  elif re.match(r'try\b', code):
83    code += '\n    pass\nexcept:\n    pass'
84  elif re.match(r'(except|finally)\b', code):
85    code = 'try:\n    pass\n' + code + '\n    pass'
86  elif re.match(r'(return|yield)\b', code):
87    code = 'def _():\n    ' + code
88  elif re.match(r'(continue|break)\b', code):
89    code = 'while True:\n    ' + code
90  elif re.match(r'print\b', code):
91    code = 'from __future__ import print_function\n' + code
92
93  return code + '\n'
94