php-parser-py 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.3
2
+ Name: php-parser-py
3
+ Version: 0.1.1
4
+ Summary: Python wrapper for PHP-Parser using cpg2py
5
+ Author: samhsu-dev
6
+ Author-email: samhsu-dev <yxu166@jh.edu>
7
+ Requires-Dist: cpg2py>=1.1.0
8
+ Requires-Dist: static-php-py
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+
12
+ # php-parser-py
13
+
14
+ Python wrapper for PHP-Parser using cpg2py's graph framework.
15
+
16
+ ## Overview
17
+
18
+ `php-parser-py` provides a Python interface to [PHP-Parser](https://github.com/nikic/PHP-Parser), enabling Abstract Syntax Tree (AST) parsing and manipulation of PHP code. The library integrates with [cpg2py](https://github.com/jhu-seclab/cpg2py)'s graph framework for powerful graph-based code analysis.
19
+
20
+ ## Features
21
+
22
+ - **Native PHP-Parser Integration**: Delegates all parsing and code generation to PHP-Parser for maximum compatibility
23
+ - **Zero-Configuration PHP**: Bundled PHP binaries via static-php-py - no system PHP installation required
24
+ - **Graph-Based Analysis**: Uses cpg2py's graph framework for AST traversal and querying
25
+ - **Dynamic Node Types**: No hardcoded PHP node types - all types come from PHP-Parser's JSON output
26
+ - **Lossless Round-Trip**: Parse → Modify → Generate code with full fidelity
27
+ - **Cross-Platform Support**: Pre-built PHP 8.4 binaries for Linux (x86_64/aarch64), macOS (x86_64/arm64), and Windows (x64)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install php-parser-py
33
+ ```
34
+
35
+ **Note**: Platform-specific wheels include pre-built PHP 8.4 binaries. No separate PHP installation required!
36
+
37
+ ## Quick Start
38
+
39
+ ### Parse PHP Code
40
+
41
+ ```python
42
+ from php_parser_py import parse
43
+
44
+ # Parse PHP code
45
+ code = """<?php
46
+ function greet($name) {
47
+ echo "Hello, $name!";
48
+ }
49
+ """
50
+
51
+ ast = parse(code)
52
+
53
+ # Query nodes by type
54
+ for func in ast.nodes(lambda n: n.label == "Stmt_Function"):
55
+ name = func.get_property("name")
56
+ print(f"Found function: {name}")
57
+ ```
58
+
59
+ ### Parse PHP File
60
+
61
+ ```python
62
+ from php_parser_py import parse_file
63
+
64
+ ast = parse_file("example.php")
65
+ ```
66
+
67
+ ### Traverse AST
68
+
69
+ ```python
70
+ # Find all echo statements
71
+ for echo in ast.nodes(lambda n: n.label == "Stmt_Echo"):
72
+ print(f"Echo at line {echo.get_property('startLine')}")
73
+
74
+ # Get children of a node
75
+ for child in ast.children(some_node):
76
+ print(f"Child type: {child.label}")
77
+
78
+ # Get all descendants
79
+ for descendant in ast.descendants(some_node):
80
+ print(f"Descendant: {descendant.label}")
81
+ ```
82
+
83
+ ### Generate PHP Code
84
+
85
+ ```python
86
+ from php_parser_py import parse, PrettyPrinter
87
+
88
+ # Parse code
89
+ ast = parse("<?php echo 'hello';")
90
+
91
+ # Modify AST (example: change properties)
92
+ # ... modify nodes as needed ...
93
+
94
+ # Generate code
95
+ printer = PrettyPrinter()
96
+ generated_code = printer.print(ast)
97
+ print(generated_code)
98
+ ```
99
+
100
+ ## Advanced Usage
101
+
102
+ ### Custom PHP Binary
103
+
104
+ If you need to use a specific PHP version or custom binary:
105
+
106
+ ```python
107
+ from php_parser_py import Parser
108
+ from pathlib import Path
109
+
110
+ # Use local PHP binary
111
+ parser = Parser(php_binary_path=Path("/usr/local/bin/php"))
112
+ ast = parser.parse("<?php echo 'hello';")
113
+
114
+ # Or download from remote URL
115
+ parser = Parser(php_binary_url="https://example.com/php-8.4.zip")
116
+ ast = parser.parse("<?php echo 'hello';")
117
+ ```
118
+
119
+ ## API Reference
120
+
121
+ ### Main Functions
122
+
123
+ - **`parse(code: str) -> AST`**: Parse PHP code string and return AST
124
+ - **`parse_file(path: str) -> AST`**: Parse PHP file and return AST
125
+
126
+ ### Classes
127
+
128
+ - **`AST`**: Represents the complete AST as a graph
129
+ - `node(id)`: Get node by ID
130
+ - `nodes(predicate)`: Iterate nodes matching predicate
131
+ - `first_node(predicate)`: Get first matching node
132
+ - `children(node)`: Get child nodes
133
+ - `descendants(node)`: Get all descendants
134
+ - `to_json()`: Reconstruct PHP-Parser JSON
135
+
136
+ - **`Node`**: Wraps a single AST node
137
+ - `id`: Node identifier
138
+ - `label`: Node type (e.g., "Stmt_Function")
139
+ - `properties`: All node properties
140
+ - `get_property(*names)`: Get property value
141
+
142
+ - **`Parser`**: Parses PHP code
143
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize parser
144
+ - `php_binary_path`: Optional path to local PHP binary
145
+ - `php_binary_url`: Optional URL to download PHP binary from
146
+ - `parse(code, prefix="")`: Parse code string
147
+ - `prefix`: Optional prefix for node IDs (useful for multi-file parsing)
148
+ - `parse_file(path, prefix=None)`: Parse file
149
+ - `prefix`: Optional prefix (auto-generated from file path if not provided)
150
+
151
+ - **`PrettyPrinter`**: Generates PHP code
152
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize printer
153
+ - `php_binary_path`: Optional path to local PHP binary
154
+ - `php_binary_url`: Optional URL to download PHP binary from
155
+ - `print(ast)`: Generate code from AST
156
+
157
+ ### Exceptions
158
+
159
+ - **`ParseError`**: Raised when PHP-Parser reports syntax error
160
+ - **`RunnerError`**: Raised when PHP execution fails
161
+
162
+ ## Architecture
163
+
164
+ The library follows a delegation-based architecture:
165
+
166
+ 1. **PHP-Parser** handles all parsing and code generation
167
+ 2. **cpg2py Storage** stores the AST as a graph
168
+ 3. **Python classes** provide convenient access to the graph
169
+
170
+ This design ensures:
171
+ - No need to maintain PHP node type definitions in Python
172
+ - Automatic compatibility with PHP-Parser updates
173
+ - Full access to PHP-Parser's features
174
+
175
+ ## Requirements
176
+
177
+ - Python >= 3.11
178
+ - cpg2py
179
+ - static-php-py (bundled PHP binaries)
180
+
181
+ ## License
182
+
183
+ MIT License - see LICENSE file for details.
184
+
185
+ ## Documentation
186
+
187
+ For detailed design documentation, see:
188
+ - [Design Document](docs/design.md)
189
+ - [Idea Document](docs/idea.md)
190
+ - [API Demo](docs/api_demo.ipynb)
@@ -0,0 +1,179 @@
1
+ # php-parser-py
2
+
3
+ Python wrapper for PHP-Parser using cpg2py's graph framework.
4
+
5
+ ## Overview
6
+
7
+ `php-parser-py` provides a Python interface to [PHP-Parser](https://github.com/nikic/PHP-Parser), enabling Abstract Syntax Tree (AST) parsing and manipulation of PHP code. The library integrates with [cpg2py](https://github.com/jhu-seclab/cpg2py)'s graph framework for powerful graph-based code analysis.
8
+
9
+ ## Features
10
+
11
+ - **Native PHP-Parser Integration**: Delegates all parsing and code generation to PHP-Parser for maximum compatibility
12
+ - **Zero-Configuration PHP**: Bundled PHP binaries via static-php-py - no system PHP installation required
13
+ - **Graph-Based Analysis**: Uses cpg2py's graph framework for AST traversal and querying
14
+ - **Dynamic Node Types**: No hardcoded PHP node types - all types come from PHP-Parser's JSON output
15
+ - **Lossless Round-Trip**: Parse → Modify → Generate code with full fidelity
16
+ - **Cross-Platform Support**: Pre-built PHP 8.4 binaries for Linux (x86_64/aarch64), macOS (x86_64/arm64), and Windows (x64)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install php-parser-py
22
+ ```
23
+
24
+ **Note**: Platform-specific wheels include pre-built PHP 8.4 binaries. No separate PHP installation required!
25
+
26
+ ## Quick Start
27
+
28
+ ### Parse PHP Code
29
+
30
+ ```python
31
+ from php_parser_py import parse
32
+
33
+ # Parse PHP code
34
+ code = """<?php
35
+ function greet($name) {
36
+ echo "Hello, $name!";
37
+ }
38
+ """
39
+
40
+ ast = parse(code)
41
+
42
+ # Query nodes by type
43
+ for func in ast.nodes(lambda n: n.label == "Stmt_Function"):
44
+ name = func.get_property("name")
45
+ print(f"Found function: {name}")
46
+ ```
47
+
48
+ ### Parse PHP File
49
+
50
+ ```python
51
+ from php_parser_py import parse_file
52
+
53
+ ast = parse_file("example.php")
54
+ ```
55
+
56
+ ### Traverse AST
57
+
58
+ ```python
59
+ # Find all echo statements
60
+ for echo in ast.nodes(lambda n: n.label == "Stmt_Echo"):
61
+ print(f"Echo at line {echo.get_property('startLine')}")
62
+
63
+ # Get children of a node
64
+ for child in ast.children(some_node):
65
+ print(f"Child type: {child.label}")
66
+
67
+ # Get all descendants
68
+ for descendant in ast.descendants(some_node):
69
+ print(f"Descendant: {descendant.label}")
70
+ ```
71
+
72
+ ### Generate PHP Code
73
+
74
+ ```python
75
+ from php_parser_py import parse, PrettyPrinter
76
+
77
+ # Parse code
78
+ ast = parse("<?php echo 'hello';")
79
+
80
+ # Modify AST (example: change properties)
81
+ # ... modify nodes as needed ...
82
+
83
+ # Generate code
84
+ printer = PrettyPrinter()
85
+ generated_code = printer.print(ast)
86
+ print(generated_code)
87
+ ```
88
+
89
+ ## Advanced Usage
90
+
91
+ ### Custom PHP Binary
92
+
93
+ If you need to use a specific PHP version or custom binary:
94
+
95
+ ```python
96
+ from php_parser_py import Parser
97
+ from pathlib import Path
98
+
99
+ # Use local PHP binary
100
+ parser = Parser(php_binary_path=Path("/usr/local/bin/php"))
101
+ ast = parser.parse("<?php echo 'hello';")
102
+
103
+ # Or download from remote URL
104
+ parser = Parser(php_binary_url="https://example.com/php-8.4.zip")
105
+ ast = parser.parse("<?php echo 'hello';")
106
+ ```
107
+
108
+ ## API Reference
109
+
110
+ ### Main Functions
111
+
112
+ - **`parse(code: str) -> AST`**: Parse PHP code string and return AST
113
+ - **`parse_file(path: str) -> AST`**: Parse PHP file and return AST
114
+
115
+ ### Classes
116
+
117
+ - **`AST`**: Represents the complete AST as a graph
118
+ - `node(id)`: Get node by ID
119
+ - `nodes(predicate)`: Iterate nodes matching predicate
120
+ - `first_node(predicate)`: Get first matching node
121
+ - `children(node)`: Get child nodes
122
+ - `descendants(node)`: Get all descendants
123
+ - `to_json()`: Reconstruct PHP-Parser JSON
124
+
125
+ - **`Node`**: Wraps a single AST node
126
+ - `id`: Node identifier
127
+ - `label`: Node type (e.g., "Stmt_Function")
128
+ - `properties`: All node properties
129
+ - `get_property(*names)`: Get property value
130
+
131
+ - **`Parser`**: Parses PHP code
132
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize parser
133
+ - `php_binary_path`: Optional path to local PHP binary
134
+ - `php_binary_url`: Optional URL to download PHP binary from
135
+ - `parse(code, prefix="")`: Parse code string
136
+ - `prefix`: Optional prefix for node IDs (useful for multi-file parsing)
137
+ - `parse_file(path, prefix=None)`: Parse file
138
+ - `prefix`: Optional prefix (auto-generated from file path if not provided)
139
+
140
+ - **`PrettyPrinter`**: Generates PHP code
141
+ - `__init__(php_binary_path=None, php_binary_url=None)`: Initialize printer
142
+ - `php_binary_path`: Optional path to local PHP binary
143
+ - `php_binary_url`: Optional URL to download PHP binary from
144
+ - `print(ast)`: Generate code from AST
145
+
146
+ ### Exceptions
147
+
148
+ - **`ParseError`**: Raised when PHP-Parser reports syntax error
149
+ - **`RunnerError`**: Raised when PHP execution fails
150
+
151
+ ## Architecture
152
+
153
+ The library follows a delegation-based architecture:
154
+
155
+ 1. **PHP-Parser** handles all parsing and code generation
156
+ 2. **cpg2py Storage** stores the AST as a graph
157
+ 3. **Python classes** provide convenient access to the graph
158
+
159
+ This design ensures:
160
+ - No need to maintain PHP node type definitions in Python
161
+ - Automatic compatibility with PHP-Parser updates
162
+ - Full access to PHP-Parser's features
163
+
164
+ ## Requirements
165
+
166
+ - Python >= 3.11
167
+ - cpg2py
168
+ - static-php-py (bundled PHP binaries)
169
+
170
+ ## License
171
+
172
+ MIT License - see LICENSE file for details.
173
+
174
+ ## Documentation
175
+
176
+ For detailed design documentation, see:
177
+ - [Design Document](docs/design.md)
178
+ - [Idea Document](docs/idea.md)
179
+ - [API Demo](docs/api_demo.ipynb)
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "php-parser-py"
3
+ version = "0.1.1"
4
+ description = "Python wrapper for PHP-Parser using cpg2py"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "samhsu-dev", email = "yxu166@jh.edu" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "cpg2py>=1.1.0",
12
+ "static-php-py",
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["uv_build>=0.9.26,<0.10.0"]
17
+ build-backend = "uv_build"
18
+
19
+ [tool.uv]
20
+ package = true
21
+
22
+ [tool.uv.build-backend]
23
+ # Include resources directory in package data
24
+ source-include = ["resources/*.zip"]
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "pytest>=9.0.2",
29
+ ]
@@ -0,0 +1,86 @@
1
+ """
2
+ php-parser-py: Python wrapper for PHP-Parser using cpg2py.
3
+
4
+ This package provides a Python interface to PHP-Parser, enabling AST parsing
5
+ and manipulation of PHP code.
6
+ """
7
+
8
+ from php_parser_py._parser import Parser as _Parser
9
+ from php_parser_py._printer import PrettyPrinter as _PrettyPrinter
10
+ from php_parser_py._resources import ensure_php_parser_extracted
11
+
12
+ # Ensure PHP-Parser is extracted on first import
13
+ ensure_php_parser_extracted()
14
+
15
+ # Import and expose public classes directly
16
+ from php_parser_py._ast import AST
17
+ from php_parser_py._node import Node
18
+ from php_parser_py._edge import Edge
19
+ from php_parser_py.exceptions import ParseError, RunnerError
20
+
21
+
22
+ class Parser(_Parser):
23
+ """Parses PHP source code using PHP-Parser.
24
+
25
+ Invokes PHP-Parser to parse PHP code and converts the resulting JSON
26
+ to cpg2py Storage format for graph-based analysis.
27
+ """
28
+
29
+
30
+ class PrettyPrinter(_PrettyPrinter):
31
+ """Converts AST to PHP code using PHP-Parser's PrettyPrinter.
32
+
33
+ Reconstructs JSON from AST graph and invokes PHP-Parser to generate
34
+ formatted PHP source code.
35
+ """
36
+
37
+
38
+ def parse(code: str) -> AST:
39
+ """Parse PHP code and return AST.
40
+
41
+ Convenience function that creates a Parser instance and parses the code.
42
+
43
+ Args:
44
+ code: PHP source code string.
45
+
46
+ Returns:
47
+ AST instance containing parsed code structure.
48
+
49
+ Raises:
50
+ ParseError: If PHP-Parser reports syntax error.
51
+ """
52
+ parser = Parser()
53
+ return parser.parse(code)
54
+
55
+
56
+ def parse_file(path: str) -> AST:
57
+ """Read and parse PHP file.
58
+
59
+ Convenience function that creates a Parser instance and parses the file.
60
+
61
+ Args:
62
+ path: File path string.
63
+
64
+ Returns:
65
+ AST instance containing parsed code structure.
66
+
67
+ Raises:
68
+ ParseError: If PHP-Parser reports syntax error.
69
+ FileNotFoundError: If file does not exist.
70
+ """
71
+ parser = Parser()
72
+ return parser.parse_file(path)
73
+
74
+
75
+ __version__ = "0.1.0"
76
+ __all__ = [
77
+ "AST",
78
+ "Node",
79
+ "Edge",
80
+ "Parser",
81
+ "PrettyPrinter",
82
+ "ParseError",
83
+ "RunnerError",
84
+ "parse",
85
+ "parse_file",
86
+ ]
@@ -0,0 +1,190 @@
1
+ """AST representation for PHP code using cpg2py's graph framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from typing import Any, Callable, Iterable, Optional
8
+
9
+ from cpg2py import AbcGraphQuerier, Storage
10
+
11
+ from php_parser_py._edge import Edge
12
+ from php_parser_py._node import Node
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class AST(AbcGraphQuerier[Node, Edge]):
18
+ """Represents a PHP Abstract Syntax Tree.
19
+
20
+ This class extends cpg2py's AbcGraphQuerier with generic type parameters
21
+ specifying that it works with Node and Edge types.
22
+
23
+ The AST is stored as a graph in cpg2py Storage, where:
24
+ - Nodes represent PHP-Parser AST nodes (statements, expressions, etc.)
25
+ - Edges represent parent-child relationships with field names
26
+
27
+ Type Parameters:
28
+ Node: The concrete node type for PHP AST nodes
29
+ Edge: The concrete edge type for parent-child relationships
30
+
31
+ Provides graph traversal methods and JSON reconstruction
32
+ for code generation.
33
+
34
+ Attributes:
35
+ _storage: cpg2py Storage containing AST nodes and edges.
36
+ """
37
+
38
+ def __init__(self, storage: Storage) -> None:
39
+ """Initialize AST with populated storage.
40
+
41
+ Args:
42
+ storage: cpg2py Storage containing AST nodes and edges.
43
+ """
44
+ super().__init__(storage)
45
+ self._storage = storage
46
+
47
+ def node(self, whose_id_is: str) -> Optional[Node]:
48
+ """Return node wrapper by ID.
49
+
50
+ Args:
51
+ whose_id_is: Node ID string.
52
+
53
+ Returns:
54
+ Node instance or None if not found.
55
+ """
56
+ if not self._storage.contains_node(whose_id_is):
57
+ return None
58
+ return Node(self._storage, whose_id_is)
59
+
60
+ def edge(self, fid: str, tid: str, eid: str) -> Optional[Edge]:
61
+ """Return edge wrapper by IDs.
62
+
63
+ Args:
64
+ fid: From node ID.
65
+ tid: To node ID.
66
+ eid: Edge type.
67
+
68
+ Returns:
69
+ Edge instance or None if not found.
70
+ """
71
+ edge_id = (fid, tid, eid)
72
+ if not self._storage.contains_edge(edge_id):
73
+ return None
74
+ return Edge(self._storage, fid, tid, eid)
75
+
76
+ def to_json(self) -> str:
77
+ """Reconstruct PHP-Parser JSON from Storage for code generation.
78
+
79
+ Traverses the graph to rebuild the nested JSON structure that matches
80
+ PHP-Parser's original output format. This enables lossless round-trip
81
+ from parsing to code generation.
82
+
83
+ Returns:
84
+ JSON string compatible with PHP-Parser's JsonDecoder.
85
+ """
86
+ root_nodes = self._find_root_nodes()
87
+ result = [self._reconstruct_node(nid) for nid in root_nodes]
88
+ return json.dumps(result)
89
+
90
+ def _find_root_nodes(self) -> list[str]:
91
+ """Find nodes with no incoming PARENT_OF edges."""
92
+ all_nodes = set(self._storage.get_nodes())
93
+ child_nodes = set()
94
+
95
+ for edge_id in self._storage.get_edges():
96
+ _, to_id, edge_type = edge_id
97
+ if edge_type == "PARENT_OF":
98
+ child_nodes.add(to_id)
99
+
100
+ root_nodes = all_nodes - child_nodes
101
+ return sorted(root_nodes)
102
+
103
+ def _reconstruct_node(self, nid: str) -> dict[str, Any]:
104
+ """Recursively reconstruct JSON object for a node."""
105
+ props = self._storage.get_node_props(nid)
106
+ if props is None:
107
+ return {}
108
+
109
+ result: dict[str, Any] = {}
110
+ node_type = props.get("nodeType")
111
+ if node_type:
112
+ result["nodeType"] = node_type
113
+
114
+ # Collect attribute fields
115
+ attributes = {}
116
+ child_fields: dict[str, Any] = {}
117
+
118
+ for key, value in props.items():
119
+ if key == "nodeType":
120
+ continue
121
+ # Attributes are typically line numbers and metadata
122
+ if key in (
123
+ "startLine",
124
+ "endLine",
125
+ "startFilePos",
126
+ "endFilePos",
127
+ "startTokenPos",
128
+ "endTokenPos",
129
+ "kind",
130
+ "comments",
131
+ ):
132
+ attributes[key] = value
133
+ else:
134
+ # All other fields (scalars, arrays, etc.) go directly in result
135
+ # Child nodes will be added separately via edges
136
+ result[key] = value
137
+
138
+ if attributes:
139
+ result["attributes"] = attributes
140
+
141
+ # Reconstruct child nodes from PARENT_OF edges
142
+ outgoing_edges = [
143
+ eid
144
+ for eid in self._storage.get_edges()
145
+ if eid[0] == nid and eid[2] == "PARENT_OF"
146
+ ]
147
+
148
+ for from_id, to_id, edge_type in outgoing_edges:
149
+ edge_props = self._storage.get_edge_props((from_id, to_id, edge_type))
150
+ if edge_props is None:
151
+ continue
152
+
153
+ field_name = edge_props.get("field")
154
+ if field_name is None:
155
+ continue
156
+
157
+ child_json = self._reconstruct_node(to_id)
158
+ index = edge_props.get("index")
159
+
160
+ if index is not None:
161
+ # Array field
162
+ if field_name not in child_fields:
163
+ child_fields[field_name] = {}
164
+ child_fields[field_name][index] = child_json
165
+ else:
166
+ # Single object field
167
+ result[field_name] = child_json
168
+
169
+ # Convert indexed dictionaries to arrays
170
+ for field_name, indexed_children in child_fields.items():
171
+ if isinstance(indexed_children, dict):
172
+ # Sort by index and create array
173
+ max_index = max(indexed_children.keys())
174
+ array = [None] * (max_index + 1)
175
+ for idx, child in indexed_children.items():
176
+ array[idx] = child
177
+ result[field_name] = array
178
+
179
+ # Add default values for common optional fields that PHP-Parser expects
180
+ # These prevent "null given, array expected" errors in PrettyPrinter
181
+ # attrGroups is required for many node types in PHP 8+
182
+ if node_type and ("attrGroups" not in result):
183
+ # Most node types that can have attributes need attrGroups
184
+ if (node_type.startswith("Stmt_") or
185
+ node_type == "Param" or
186
+ node_type.startswith("Expr_Closure") or
187
+ node_type.startswith("Expr_ArrowFunction")):
188
+ result["attrGroups"] = []
189
+
190
+ return result