0

我编写了一个库,使用 justastinspect库来解析和发出 [在 Python < 3.9 上使用astor ] 内部 Python 构造。

刚刚意识到我真的需要保留评论。最好不要求助于RedBaronLibCST;因为我只需要发出未更改的评论;是否有一种简洁的方式来保留注释解析/发出 Python 源代码,仅使用 stdlib

4

2 回答 2

0

我最终做的是编写一个简单的解析器,在 339 个源代码行中没有元语言: https ://github.com/offscale/cdd-python/blob/master/cdd/cst_utils.py

具体语法树的实现[列表!]

  1. 逐个字符读取源文件;
  2. 一旦检测到语句结束†,将语句类型添加到一维列表中;
    • † end of line if line.lstrip().startswith("#") or line not endswith('\\') and balanced_parens(line)else 继续咀嚼,直到该条件为真……加上多行字符串等周围的一些边缘情况;
  3. 一旦完成,就会有一个大(一维)列表,其中每个元素都是一个namedtuple具有value属性的列表。

与内置抽象语法树集成ast

  1. ast将要修改(而不是删除)的节点限制为: { ClassDefAsyncFunctionDefFunctionDef} 文档字符串(第一个正文元素Constant| StrAssignAnnAssign;
  2. cst_idx, cst_node = find_cst_at_ast(cst_list, _node);
  3. 如果 doc_str 节点则maybe_replace_doc_str_in_function_or_class(_node, cst_idx, cst_list)
  4. …</li>
  5. 现在,cst_list仅包含对上述节点的更改,并且仅当更改超过空格时,才能创建为"".join(map(attrgetter("value"), cst_list))用于输出eval或直接输出到源文件的字符串(例如,就地覆盖)。

质量控制

  1. 100% 测试覆盖率
  2. 100% 文档覆盖率
  3. 支持最后 6 个版本的 Python(包括最新的 alpha)
  4. CI/CD
  5. (Apache-2.0 或 MIT) 许可

限制

  1. 缺乏元语言,特别是缺乏使用Python 提供的 语法意味着不会自动支持新的语法元素(match/case是支持的,但如果引入了新语法,它 [尚未?] 支持......至少不会自动支持);
  2. 不是 stdlib 内置的,因此 stdlib 可能会破坏兼容性;
  3. [可能] 不支持删除节点;
  4. 如果存在阴影变量或 linter 应该指出的类似问题,则可能会错误地识别节点。
于 2022-03-03T18:08:30.663 回答
-1

可以通过使用标记器捕获它们将它们合并回生成的源代码来保留注释。

给定程序变量中的玩具程序,我们可以演示注释如何在 AST 中丢失:

import ast

program = """
# This comment lost
p1v = 4 + 4
p1l = ['a', # Implicit line joining comment for a lost
       'b'] # Ending comment for b lost
def p1f(x):
    "p1f docstring"
    # Comment in function p1f lost
    return x
print(p1f(p1l), p1f(p1v))
"""
tree = ast.parse(program)
print('== Full program code:')
print(ast.unparse(tree))

输出显示所有评论都消失了:

== Full program code:
p1v = 4 + 4
p1l = ['a', 'b']

def p1f(x):
    """p1f docstring"""
    return x
print(p1f(p1l), p1f(p1v))

但是,如果我们使用分词器扫描评论,我们可以使用它来合并评论:

from io import StringIO
import tokenize

def scan_comments(source):
    """ Scan source code file for relevant comments
    """
    # Find token for comments
    for k,v in tokenize.tok_name.items():
        if v == 'COMMENT':
            comment = k
            break
    comtokens = []
    with StringIO(source) as f:
        tokens = tokenize.generate_tokens(f.readline)
        for token in tokens:
            if token.type != comment:
                continue
            comtokens += [token]
    return comtokens

comtokens = scan_comments(program)
print('== Comment after p1l[0]\n\t', comtokens[1])

输出(编辑为分割长线):

== Comment after p1l[0]
     TokenInfo(type=60 (COMMENT),
               string='# Implicit line joining comment for a lost',
               start=(4, 12), end=(4, 54),
               line="p1l = ['a', # Implicit line joining comment for a lost\n")

使用稍微修改的版本ast.unparse(),替换方法maybe_newline()traverse()修改版本,您应该能够使用来自评论扫描器(开始变量)的位置信息,结合来自 AST 的位置信息,在它们的大致位置合并所有评论; 大多数节点都有一个lineno属性。

不完全是。参见例如列表变量赋值。源代码分为两行,但ast.unparse() 只生成一行(参见第二个代码段中的输出)。

此外,您需要确保ast.increment_lineno()在添加代码后使用更新 AST 中的位置信息。

似乎 maybe_newline()库代码(或其替换)中可能需要更多调用。

于 2021-12-26T01:35:39.737 回答