|
3 | 3 | import ast
|
4 | 4 | import re
|
5 | 5 | import sys
|
| 6 | +from copy import copy |
6 | 7 | from keyword import kwlist
|
7 | 8 | from pathlib import Path
|
| 9 | +from typing import Callable |
8 | 10 |
|
9 | 11 | import click
|
10 | 12 |
|
@@ -46,43 +48,66 @@ def generate_rewrite(file: Path, source: str) -> str | None:
|
46 | 48 | def find_nodes_to_change(tree: ast.AST) -> list[ChangedNode]:
|
47 | 49 | changed: list[ChangedNode] = []
|
48 | 50 | for el_info in find_element_constructor_usages(tree):
|
49 |
| - if isinstance(el_info.props, ast.Dict): |
50 |
| - did_change = False |
51 |
| - keys: list[ast.expr | None] = [] |
52 |
| - for k in el_info.props.keys: |
53 |
| - if isinstance(k, ast.Constant) and isinstance(k.value, str): |
54 |
| - new_prop_name = conv_attr_name(k.value) |
55 |
| - if new_prop_name != k.value: |
56 |
| - did_change = True |
57 |
| - keys.append(ast.Constant(new_prop_name)) |
58 |
| - else: |
59 |
| - keys.append(k) |
60 |
| - else: |
61 |
| - keys.append(k) |
62 |
| - if not did_change: |
63 |
| - continue |
64 |
| - el_info.props.keys = keys |
65 |
| - else: |
66 |
| - did_change = False |
67 |
| - keywords: list[ast.keyword] = [] |
68 |
| - for kw in el_info.props.keywords: |
69 |
| - if kw.arg is not None: |
70 |
| - new_prop_name = conv_attr_name(kw.arg) |
71 |
| - if new_prop_name != kw.arg: |
72 |
| - did_change = True |
73 |
| - keywords.append(ast.keyword(arg=new_prop_name, value=kw.value)) |
74 |
| - else: |
75 |
| - keywords.append(kw) |
76 |
| - else: |
77 |
| - keywords.append(kw) |
78 |
| - if not did_change: |
79 |
| - continue |
80 |
| - el_info.props.keywords = keywords |
81 |
| - |
82 |
| - changed.append(ChangedNode(el_info.call, el_info.parents)) |
| 51 | + if _rewrite_props(el_info.props, _construct_prop_item): |
| 52 | + changed.append(ChangedNode(el_info.call, el_info.parents)) |
83 | 53 | return changed
|
84 | 54 |
|
85 | 55 |
|
86 | 56 | def conv_attr_name(name: str) -> str:
|
87 | 57 | new_name = CAMEL_CASE_SUB_PATTERN.sub("_", name).lower()
|
88 | 58 | return f"{new_name}_" if new_name in kwlist else new_name
|
| 59 | + |
| 60 | + |
| 61 | +def _construct_prop_item(key: str, value: ast.expr) -> tuple[str, ast.expr]: |
| 62 | + if key == "style" and isinstance(value, (ast.Dict, ast.Call)): |
| 63 | + new_value = copy(value) |
| 64 | + if _rewrite_props( |
| 65 | + new_value, |
| 66 | + lambda k, v: ( |
| 67 | + (k, v) |
| 68 | + # avoid infinite recursion |
| 69 | + if k == "style" |
| 70 | + else _construct_prop_item(k, v) |
| 71 | + ), |
| 72 | + ): |
| 73 | + value = new_value |
| 74 | + else: |
| 75 | + key = conv_attr_name(key) |
| 76 | + return key, value |
| 77 | + |
| 78 | + |
| 79 | +def _rewrite_props( |
| 80 | + props_node: ast.Dict | ast.Call, |
| 81 | + constructor: Callable[[str, ast.expr], tuple[str, ast.expr]], |
| 82 | +) -> bool: |
| 83 | + if isinstance(props_node, ast.Dict): |
| 84 | + did_change = False |
| 85 | + keys: list[ast.expr | None] = [] |
| 86 | + values: list[ast.expr] = [] |
| 87 | + for k, v in zip(props_node.keys, props_node.values): |
| 88 | + if isinstance(k, ast.Constant) and isinstance(k.value, str): |
| 89 | + k_value, new_v = constructor(k.value, v) |
| 90 | + if k_value != k.value or new_v is not v: |
| 91 | + did_change = True |
| 92 | + k = ast.Constant(value=k_value) |
| 93 | + v = new_v |
| 94 | + keys.append(k) |
| 95 | + values.append(v) |
| 96 | + if not did_change: |
| 97 | + return False |
| 98 | + props_node.keys = keys |
| 99 | + props_node.values = values |
| 100 | + else: |
| 101 | + did_change = False |
| 102 | + keywords: list[ast.keyword] = [] |
| 103 | + for kw in props_node.keywords: |
| 104 | + if kw.arg is not None: |
| 105 | + kw_arg, kw_value = constructor(kw.arg, kw.value) |
| 106 | + if kw_arg != kw.arg or kw_value is not kw.value: |
| 107 | + did_change = True |
| 108 | + kw = ast.keyword(arg=kw_arg, value=kw_value) |
| 109 | + keywords.append(kw) |
| 110 | + if not did_change: |
| 111 | + return |
| 112 | + props_node.keywords = keywords |
| 113 | + return True |
0 commit comments