python-code-validator 0.1.1__py3-none-any.whl

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,319 @@
1
+ """Contains concrete implementations of all Selector components.
2
+
3
+ Each class in this module implements the `Selector` protocol and is responsible
4
+ for finding and returning specific types of nodes from an Abstract Syntax Tree.
5
+ They use `ast.walk` to traverse the tree and can be constrained to specific
6
+ scopes using the ScopedSelector base class.
7
+ """
8
+
9
+ import ast
10
+ from typing import Any
11
+
12
+ from ..components.ast_utils import get_full_name
13
+ from ..components.definitions import Selector
14
+ from ..components.scope_handler import find_scope_node
15
+
16
+
17
+ class ScopedSelector(Selector):
18
+ """An abstract base class for selectors that support scoping.
19
+
20
+ This class provides a common mechanism for subclasses to narrow their search
21
+ to a specific part of the AST (e.g., a single function or class) before
22
+ performing their selection logic.
23
+
24
+ Attributes:
25
+ in_scope_config (dict | str | None): The configuration dictionary or
26
+ string that defines the desired scope.
27
+ """
28
+
29
+ def __init__(self, **kwargs: Any):
30
+ """Initializes the ScopedSelector.
31
+
32
+ Args:
33
+ **kwargs: Keyword arguments containing the scope configuration.
34
+ Expects `in_scope` key.
35
+ """
36
+ self.in_scope_config = kwargs.get("in_scope")
37
+
38
+ def _get_search_tree(self, tree: ast.Module) -> ast.AST | None:
39
+ """Determines the root node for the search based on the scope config.
40
+
41
+ If no scope is defined or if it's 'global', the whole tree is used.
42
+ Otherwise, it uses `find_scope_node` to locate the specific subtree.
43
+
44
+ Args:
45
+ tree: The root of the full AST.
46
+
47
+ Returns:
48
+ The AST node to start the search from, or None if the scope
49
+ could not be found.
50
+ """
51
+ if not self.in_scope_config or self.in_scope_config == "global":
52
+ return tree
53
+
54
+ scope_node = find_scope_node(tree, self.in_scope_config)
55
+ return scope_node
56
+
57
+ def select(self, tree: ast.Module) -> list[ast.AST]:
58
+ """Abstract select method to be implemented by subclasses."""
59
+ raise NotImplementedError
60
+
61
+
62
+ class FunctionDefSelector(ScopedSelector):
63
+ """Selects function definition (`def`) nodes from an AST.
64
+
65
+ JSON Params:
66
+ name (str): The name of the function to find. Use "*" to find all.
67
+ """
68
+
69
+ def __init__(self, **kwargs: Any):
70
+ super().__init__(**kwargs)
71
+ self.name_to_find = kwargs.get("name")
72
+
73
+ def select(self, tree: ast.Module) -> list[ast.AST]:
74
+ """Finds all `ast.FunctionDef` nodes that match the name criteria."""
75
+ search_tree = self._get_search_tree(tree)
76
+ if not search_tree:
77
+ return []
78
+
79
+ found_nodes: list[ast.AST] = []
80
+ for node in ast.walk(search_tree):
81
+ if isinstance(node, ast.FunctionDef):
82
+ if self.name_to_find == "*" or node.name == self.name_to_find:
83
+ found_nodes.append(node)
84
+ return found_nodes
85
+
86
+
87
+ class ClassDefSelector(ScopedSelector):
88
+ """Selects class definition (`class`) nodes from an AST.
89
+
90
+ JSON Params:
91
+ name (str): The name of the class to find. Use "*" to find all.
92
+ """
93
+
94
+ def __init__(self, **kwargs: Any):
95
+ """Initializes the selector."""
96
+ super().__init__(**kwargs)
97
+ self.name_to_find = kwargs.get("name")
98
+
99
+ def select(self, tree: ast.Module) -> list[ast.AST]:
100
+ """Finds all `ast.ClassDef` nodes that match the name criteria."""
101
+ search_tree = self._get_search_tree(tree)
102
+ if not search_tree:
103
+ return []
104
+
105
+ found_nodes: list[ast.AST] = []
106
+ for node in ast.walk(search_tree):
107
+ if isinstance(node, ast.ClassDef):
108
+ if self.name_to_find == "*" or node.name == self.name_to_find:
109
+ found_nodes.append(node)
110
+ return found_nodes
111
+
112
+
113
+ class ImportStatementSelector(ScopedSelector):
114
+ """Selects import nodes (`import` or `from...import`) from an AST.
115
+
116
+ JSON Params:
117
+ name (str): The name of the module to find (e.g., "os", "requests").
118
+ """
119
+
120
+ def __init__(self, **kwargs: Any):
121
+ super().__init__(**kwargs)
122
+ self.module_name_to_find = kwargs.get("name")
123
+
124
+ def select(self, tree: ast.Module) -> list[ast.AST]:
125
+ """Finds all import-related nodes that match the name criteria."""
126
+ if not self.module_name_to_find:
127
+ return []
128
+
129
+ search_tree = self._get_search_tree(tree)
130
+ if not search_tree:
131
+ return []
132
+
133
+ found_nodes: list[ast.AST] = []
134
+ for node in ast.walk(search_tree):
135
+ if isinstance(node, ast.Import):
136
+ for alias in node.names:
137
+ # Проверяем 'os' в 'import os.path'
138
+ module_parts = alias.name.split(".")
139
+ if alias.name.startswith(self.module_name_to_find) or self.module_name_to_find in module_parts:
140
+ found_nodes.append(node)
141
+ break
142
+ elif isinstance(node, ast.ImportFrom):
143
+ if node.module and node.module.startswith(self.module_name_to_find):
144
+ found_nodes.append(node)
145
+
146
+ return found_nodes
147
+
148
+
149
+ class FunctionCallSelector(ScopedSelector):
150
+ """Selects function call nodes from an AST.
151
+
152
+ This can find simple function calls (`my_func()`) and method calls
153
+ (`requests.get()`).
154
+
155
+ JSON Params:
156
+ name (str): The full name of the function being called.
157
+ """
158
+
159
+ def __init__(self, **kwargs: Any):
160
+ """Initializes the selector."""
161
+ super().__init__(**kwargs)
162
+ self.name_to_find = kwargs.get("name")
163
+
164
+ def select(self, tree: ast.Module) -> list[ast.AST]:
165
+ """Finds all `ast.Call` nodes that match the name criteria."""
166
+ search_tree = self._get_search_tree(tree)
167
+ if not search_tree:
168
+ return []
169
+
170
+ found_nodes: list[ast.AST] = []
171
+ for node in ast.walk(search_tree):
172
+ if isinstance(node, ast.Call):
173
+ # Используем наш helper, чтобы получить полное имя вызываемого объекта
174
+ full_name = get_full_name(node.func)
175
+ if full_name and full_name == self.name_to_find:
176
+ found_nodes.append(node)
177
+ return found_nodes
178
+
179
+
180
+ class AssignmentSelector(ScopedSelector):
181
+ """Selects assignment nodes (`=` or `:=` or type-annotated).
182
+
183
+ This can find assignments to simple variables (`x = 5`) and attributes
184
+ (`self.player = ...`).
185
+
186
+ JSON Params:
187
+ name (str): The full name of the variable or attribute being assigned to.
188
+ """
189
+
190
+ def __init__(self, **kwargs: Any):
191
+ super().__init__(**kwargs)
192
+ self.target_name_to_find = kwargs.get("name")
193
+
194
+ def select(self, tree: ast.Module) -> list[ast.AST]:
195
+ """Finds all `ast.Assign` or `ast.AnnAssign` nodes matching the target name."""
196
+ search_tree = self._get_search_tree(tree)
197
+ if not search_tree:
198
+ return []
199
+
200
+ found_nodes: list[ast.AST] = []
201
+ for node in ast.walk(search_tree):
202
+ # Мы поддерживаем и простое присваивание (x=5), и с аннотацией (x: int = 5)
203
+ if isinstance(node, (ast.Assign, ast.AnnAssign)):
204
+ # Целей присваивания может быть несколько (a = b = 5)
205
+ targets = node.targets if isinstance(node, ast.Assign) else [node.target]
206
+ for target in targets:
207
+ full_name = get_full_name(target)
208
+ if full_name and (self.target_name_to_find == "*" or full_name == self.target_name_to_find):
209
+ found_nodes.append(node)
210
+ return found_nodes
211
+
212
+
213
+ class UsageSelector(ScopedSelector):
214
+ """Selects nodes where a variable or attribute is used (read).
215
+
216
+ This finds nodes in a "load" context, meaning the value of the variable
217
+ is being accessed, not assigned.
218
+
219
+ JSON Params:
220
+ name (str): The name of the variable or attribute being used.
221
+ """
222
+
223
+ def __init__(self, **kwargs: Any):
224
+ super().__init__(**kwargs)
225
+ self.variable_name_to_find = kwargs.get("name")
226
+
227
+ def select(self, tree: ast.Module) -> list[ast.AST]:
228
+ """Finds all `ast.Name` nodes (in load context) matching the name."""
229
+ search_tree = self._get_search_tree(tree)
230
+ if not search_tree:
231
+ return []
232
+
233
+ found_nodes: list[ast.AST] = []
234
+ for node in ast.walk(search_tree):
235
+ # Проверяем и простые имена, и атрибуты, когда их "читают"
236
+ if isinstance(node, (ast.Name, ast.Attribute)) and isinstance(getattr(node, "ctx", None), ast.Load):
237
+ full_name = get_full_name(node)
238
+ if full_name and full_name == self.variable_name_to_find:
239
+ found_nodes.append(node)
240
+ return found_nodes
241
+
242
+
243
+ class LiteralSelector(ScopedSelector):
244
+ """Selects literal nodes (e.g., numbers, strings), ignoring docstrings.
245
+
246
+ JSON Params:
247
+ name (str): The type of literal to find. Supported: "number", "string".
248
+ """
249
+
250
+ def __init__(self, **kwargs: Any):
251
+ super().__init__(**kwargs)
252
+ self.literal_type = kwargs.get("name") # 'name' - это наш унифицированный ключ
253
+
254
+ def select(self, tree: ast.Module) -> list[ast.AST]:
255
+ search_tree = self._get_search_tree(tree)
256
+ if not search_tree:
257
+ return []
258
+
259
+ type_map = {"number": (int, float), "string": (str,)}
260
+ expected_py_types = type_map.get(self.literal_type)
261
+ if not expected_py_types:
262
+ return []
263
+
264
+ found_nodes: list[ast.AST] = []
265
+ for node in ast.walk(search_tree):
266
+ # Мы ищем только узлы Constant
267
+ if not isinstance(node, ast.Constant):
268
+ continue
269
+
270
+ # Проверяем тип значения внутри константы
271
+ if not isinstance(node.value, expected_py_types):
272
+ continue
273
+
274
+ # Пропускаем докстринги
275
+ if hasattr(node, "parent") and isinstance(node.parent, ast.Expr):
276
+ continue
277
+
278
+ # Пропускаем f-строки
279
+ if hasattr(node, "parent") and isinstance(node.parent, ast.JoinedStr):
280
+ continue
281
+
282
+ found_nodes.append(node)
283
+
284
+ return found_nodes
285
+
286
+
287
+ class AstNodeSelector(ScopedSelector):
288
+ """A generic selector for finding any AST node by its class name.
289
+
290
+ This is a powerful, low-level selector for advanced use cases.
291
+
292
+ JSON Params:
293
+ node_type (str | list[str]): The name(s) of the AST node types to find,
294
+ as defined in the `ast` module (e.g., "For", "While", "Try").
295
+ """
296
+
297
+ def __init__(self, **kwargs: Any):
298
+ super().__init__(**kwargs)
299
+ node_type_arg = kwargs.get("node_type")
300
+
301
+ # Поддерживаем и одну строку, и список строк
302
+ if isinstance(node_type_arg, list):
303
+ self.node_types_to_find = tuple(getattr(ast, nt) for nt in node_type_arg if hasattr(ast, nt))
304
+ elif isinstance(node_type_arg, str) and hasattr(ast, node_type_arg):
305
+ self.node_types_to_find = (getattr(ast, node_type_arg),)
306
+ else:
307
+ self.node_types_to_find = ()
308
+
309
+ def select(self, tree: ast.Module) -> list[ast.AST]:
310
+ """Finds all AST nodes that are instances of the specified types."""
311
+ search_tree = self._get_search_tree(tree)
312
+ if not search_tree or not self.node_types_to_find:
313
+ return []
314
+
315
+ found_nodes: list[ast.AST] = []
316
+ for node in ast.walk(search_tree):
317
+ if isinstance(node, self.node_types_to_find):
318
+ found_nodes.append(node)
319
+ return found_nodes
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-code-validator
3
+ Version: 0.1.1
4
+ Summary: A flexible framework for static validation of Python code based on JSON rules.
5
+ Author-email: Qu1nel <covach.qn@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Ivan Kovach
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/Qu1nel/PythonCodeValidator
29
+ Project-URL: Bug Tracker, https://github.com/Qu1nel/PythonCodeValidator/issues
30
+ Keywords: validation,linter,static analysis,testing,education
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.11
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Requires-Python: >=3.11
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Provides-Extra: dev
41
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
42
+ Requires-Dist: flake8>=7.0.0; extra == "dev"
43
+ Requires-Dist: build; extra == "dev"
44
+ Requires-Dist: twine; extra == "dev"
45
+ Requires-Dist: coverage>=7.5.0; extra == "dev"
46
+ Provides-Extra: docs
47
+ Requires-Dist: sphinx>=7.0.0; extra == "docs"
48
+ Requires-Dist: furo; extra == "docs"
49
+ Requires-Dist: myst-parser; extra == "docs"
50
+ Requires-Dist: sphinx-design; extra == "docs"
51
+ Dynamic: license-file
52
+
53
+ <div align="center">
54
+ <br/>
55
+ <!-- <img src=".github/assets/logo.png" alt="logo" width="200" height="auto" /> -->
56
+ <h1>Python Code Validator</h1>
57
+ <p>
58
+ <b>A flexible, AST-based framework for static validation of Python code using declarative JSON rules.</b>
59
+ </p>
60
+ <br/>
61
+
62
+ <!-- Badges -->
63
+ <p>
64
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/stargazers"><img src="https://img.shields.io/github/stars/Qu1nel/PythonCodeValidator" alt="GitHub Stars"></a>
65
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/network/members"><img src="https://img.shields.io/github/forks/Qu1nel/PythonCodeValidator" alt="GitHub Forks"></a>
66
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/graphs/contributors"><img src="https://img.shields.io/github/contributors/Qu1nel/PythonCodeValidator" alt="Contributors"></a>
67
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/issues/"><img src="https://img.shields.io/github/issues/Qu1nel/PythonCodeValidator" alt="Open Issues"></a>
68
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/commits/main"><img src="https://img.shields.io/github/last-commit/Qu1nel/PythonCodeValidator" alt="Last Commit"></a>
69
+ </p>
70
+ <p>
71
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/actions/workflows/ci.yml"><img src="https://github.com/Qu1nel/PythonCodeValidator/actions/workflows/ci.yml/badge.svg" alt="CI Status"></a>
72
+ <a href="https://app.codecov.io/gh/Qu1nel/PythonCodeValidator"><img src="https://codecov.io/gh/Qu1nel/PythonCodeValidator/graph/badge.svg" alt="Coverage"></a>
73
+ <a href="https://pypi.org/project/python-code-validator/"><img src="https://img.shields.io/pypi/v/python-code-validator.svg" alt="PyPI Version"></a>
74
+ <a href="https://pypi.org/project/python-code-validator/"><img src="https://img.shields.io/pypi/pyversions/python-code-validator.svg" alt="Python Versions"></a>
75
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Qu1nel/PythonCodeValidator" alt="License"></a>
76
+ </p>
77
+
78
+ <h4>
79
+ <a href="#-quick-usage-example">Usage Examples</a>
80
+ <span>·</span>
81
+ <a href="https://[your-project].readthedocs.io">Full Documentation</a>
82
+ <span>·</span>
83
+ <a href="#">AI documentation</a>
84
+ <span>·</span>
85
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/blob/main/docs/how_it_works/index.md">Developer's Guide</a>
86
+ <span>·</span>
87
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/issues/new?template=1-bug-report.md">Report a Bug</a>
88
+ <span>·</span>
89
+ <a href="https://github.com/Qu1nel/PythonCodeValidator/issues/new?template=4-feature-request.md">Request Feature</a>
90
+ </h4>
91
+ </div>
92
+
93
+ <br/>
94
+
95
+ ---
96
+
97
+ ## Table of Contents
98
+
99
+ - [About The Project](#-about-the-project)
100
+ - [The Power of Combinatorics](#-the-power-of-combinatorics)
101
+ - [Key Features](#-key-features)
102
+ - [Getting Started](#-getting-started)
103
+ - [Installation](#installation)
104
+ - [Usage Examples](#-quick-usage-example)
105
+ - [Example 1: Simple Check](#example-1-simple-check)
106
+ - [Example 2: Advanced Check](#example-2-advanced-check)
107
+ - [Documentation](#-documentation)
108
+ - [Contributing](#-contributing)
109
+ - [License](#-license)
110
+ - [Contact](#Contact)
111
+
112
+ ## 📖 About The Project
113
+
114
+ **Python Code Validator** is an engine designed for educational platforms and automated testing systems. It solves a key
115
+ problem: **how to verify that a student's code meets specific structural and stylistic requirements *before* running
116
+ resource-intensive dynamic tests.**
117
+
118
+ Instead of writing complex Python scripts for each new validation rule, you can define them declaratively in a simple,
119
+ powerful **JSON format**. This allows teachers and curriculum developers to easily create and adapt validation scenarios
120
+ without deep programming knowledge. The framework analyzes the code's Abstract Syntax Tree (AST), providing a deep and
121
+ reliable way to enforce best practices.
122
+
123
+ ## 📈 The Power of Combinatorics
124
+
125
+ The framework's power lies in its combinatorial architecture. It is built on a small set of primitive "bricks":
126
+ **Selectors** ($S$) that define *what* to find in the code, and **Constraints** ($C$) that define *what condition* to
127
+ check.
128
+
129
+ The number of unique validation rules ($R$) is not a sum, but a product of these components. A single rule can be
130
+ represented as:
131
+
132
+ $$R_{\text{single}} = S \times C$$
133
+
134
+ With approximately $10$ types of selectors and $10$ types of constraints, this already provides ~$100$ unique checks.
135
+ However,
136
+ the true flexibility comes from logical composition, allowing for a near-infinite number of validation scenarios:
137
+
138
+ $$R_{\text{total}} \approx S \times \sum_{k=1}^{|C|} \binom{|C|}{k} = S \times (2^{|C|} - 1)$$
139
+
140
+ This design provides **thousands of potential validation scenarios** out-of-the-box, offering extreme flexibility with
141
+ minimal complexity.
142
+
143
+ ## ✨ Key Features
144
+
145
+ - **Declarative JSON Rules**: Define validation logic in a human-readable format.
146
+ - **Powerful Static Analysis**:
147
+ - ✅ Check syntax and PEP8 compliance (`flake8`).
148
+ - ✅ Enforce or forbid specific `import` statements.
149
+ - ✅ Verify class structure, inheritance, and function signatures.
150
+ - ✅ Forbid "magic numbers" or specific function calls like `eval`.
151
+ - **Precise Scoping**: Apply rules globally, or narrowly to a specific function, class, or method.
152
+ - **Extensible Architecture**: Easily add new, custom checks by creating new Selector or Constraint components.
153
+
154
+ ## 🚀 Getting Started
155
+
156
+ ### Installation
157
+
158
+ **1. For Users (from PyPI):**
159
+
160
+ Install the package with one command. This will make the `validate-code` command-line tool available.
161
+
162
+ ```bash
163
+ pip install python-code-validator
164
+ ```
165
+
166
+ **2. For Users (from source):**
167
+
168
+ If you want to install directly from the repository:
169
+
170
+ ```bash
171
+ git clone https://github.com/Qu1nel/PythonCodeValidator.git
172
+ cd PythonCodeValidator
173
+ pip install .
174
+ ```
175
+
176
+ **3. For Developers:**
177
+
178
+ To set up a full development environment, see the [Contributing Guidelines](./CONTRIBUTING.md).
179
+
180
+ ## ⚡ Quick Usage Example
181
+
182
+ The validator is a command-line tool named `validate-code`.
183
+
184
+ ### Example 1: Simple Check
185
+
186
+ Let's check if a required function exists.
187
+
188
+ **`solution_simple.py`:**
189
+
190
+ ```python
191
+ # This file is missing the 'solve' function
192
+ def main():
193
+ print("Hello")
194
+ ```
195
+
196
+ **`rules_simple.json`:**
197
+
198
+ ```json
199
+ {
200
+ "validation_rules": [
201
+ {
202
+ "rule_id": 1,
203
+ "message": "Required function 'solve' is missing.",
204
+ "check": {
205
+ "selector": {
206
+ "type": "function_def",
207
+ "name": "solve"
208
+ },
209
+ "constraint": {
210
+ "type": "is_required"
211
+ }
212
+ }
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ **Running the validator:**
219
+
220
+ ```bash
221
+ $ validate-code solution_simple.py rules_simple.json
222
+ Starting validation for: solution_simple.py
223
+ Required function 'solve' is missing.
224
+ Validation failed.
225
+ ```
226
+
227
+ ### Example 2: Advanced Check
228
+
229
+ Let's enforce a complex rule: "In our game class, the `update` method must not contain any `print` statements."
230
+
231
+ **`game.py`:**
232
+
233
+ ```python
234
+ import arcade
235
+
236
+
237
+ class MyGame(arcade.Window):
238
+ def update(self, delta_time):
239
+ print("Debugging player position...") # Forbidden call
240
+ self.player.x += 1
241
+ ```
242
+
243
+ **`rules_advanced.json`:**
244
+
245
+ ```json
246
+ {
247
+ "validation_rules": [
248
+ {
249
+ "rule_id": 101,
250
+ "message": "Do not use 'print' inside the 'update' method.",
251
+ "check": {
252
+ "selector": {
253
+ "type": "function_call",
254
+ "name": "print",
255
+ "in_scope": {
256
+ "class": "MyGame",
257
+ "method": "update"
258
+ }
259
+ },
260
+ "constraint": {
261
+ "type": "is_forbidden"
262
+ }
263
+ }
264
+ }
265
+ ]
266
+ }
267
+ ```
268
+
269
+ **Running the validator:**
270
+
271
+ ```bash
272
+ $ validate-code game.py rules_advanced.json
273
+ Starting validation for: game.py
274
+ Do not use 'print' inside the 'update' method.
275
+ Validation failed.
276
+ ```
277
+
278
+ ## 📚 Documentation
279
+
280
+ - **Full User Guide & JSON Specification**: Our complete documentation is hosted on
281
+ **[Read the Docs](https://[your-project].readthedocs.io)**.
282
+ - **Developer's Guide**: For a deep dive into the architecture, see the
283
+ **[How It Works guide](./docs/how_it_works/index.md)**.
284
+ - **Interactive AI-Powered Docs**: *(Coming Soon)* An interactive documentation experience.
285
+
286
+ ## 🤝 Contributing
287
+
288
+ Contributions make the open-source community an amazing place to learn, inspire, and create. Any contributions you make
289
+ are **greatly appreciated**.
290
+
291
+ Please read our **[Contributing Guidelines](./CONTRIBUTING.md)** to get started. This project adheres to the
292
+ **[Code of Conduct](./CODE_OF_CONDUCT.md)**.
293
+
294
+ ## 📜 License
295
+
296
+ Distributed under the MIT License. See `LICENSE` for more information.
297
+
298
+ ---
299
+
300
+ ### Contact
301
+
302
+ Developed by **[Ivan Kovach (@Qu1nel)](https://github.com/Qu1nel)**.
303
+
304
+ Email: **[covach.qn@gmail.com](mailto:covach.qn@gmail.com)** Telegram: **[@qnllnq](https://t.me/qnllnq)**
305
+
306
+ <br/>
307
+
308
+ <p align="right"><a href="./LICENSE">MIT</a> © <a href="https://github.com/Qu1nel/">Ivan Kovach</a></p>
@@ -0,0 +1,22 @@
1
+ code_validator/__init__.py,sha256=bOv6iOVODFBTt2Y0lC6hYCI2XAfex3HKaNoyT-HwUG4,963
2
+ code_validator/__main__.py,sha256=c7P8Lz3EuwYRHarTEp_DExMUauod9X42p6aTZkpvi10,330
3
+ code_validator/cli.py,sha256=fLbSP_4ABZGZTH6-L4oAXKVRTW-OVMdpcZgT9F4ibtY,3466
4
+ code_validator/config.py,sha256=kTqD8-SFbtUQ9oif-TylqNFPadTq5JSGBv84cI0R8dY,2657
5
+ code_validator/core.py,sha256=4sPdlunmODfNpDrP9QH2jIrGFVLFlo_r8MAoB4_63vo,4560
6
+ code_validator/exceptions.py,sha256=XkiRNQ25FWJkjS2wBaUaKQcEL5WF9tN_HSV3tqJwDcE,1627
7
+ code_validator/output.py,sha256=VRJLGwm6X9i8SnovosNrHu96ueZXz9GIvUQXy4xDtHw,3304
8
+ code_validator/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ code_validator/components/ast_utils.py,sha256=7DzyKmLekj_qtuZcS8BrcWXqQXVEPPQ_rwh3BmpIk9o,1412
10
+ code_validator/components/definitions.py,sha256=cFbKL7UZYHqWjz2gJCmZ6fU_ZdQ5L_h1tlQBYkxSO7Q,3126
11
+ code_validator/components/factories.py,sha256=qrQotS7lyqIGhoNGRvSNQKy6p9YJKQC7YaPi4eCtpww,10436
12
+ code_validator/components/scope_handler.py,sha256=vb4pCE-4DyxGkRYGeW2JWxP2IiZH3uRRfReo0IBgM5Y,2459
13
+ code_validator/rules_library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ code_validator/rules_library/basic_rules.py,sha256=0K45Ed8yjy-pXDZpI1Xvgj94zvaFz-PucFRz__X4uRI,6652
15
+ code_validator/rules_library/constraint_logic.py,sha256=X95g0673hoZey3LGCBH6AscbIuHYq_aY9WRKIkJhOnk,9525
16
+ code_validator/rules_library/selector_nodes.py,sha256=JxhUBXS95Dad_60Ut-8XkW2MFM-7bkeRb_yk7vXlNPE,12200
17
+ python_code_validator-0.1.1.dist-info/licenses/LICENSE,sha256=Lq69RwIO4Dge7OsjgAamJfYSDq2DWI2yzVYI1VX1s6c,1089
18
+ python_code_validator-0.1.1.dist-info/METADATA,sha256=e_wxbF2xUH4kdixYAAktx_J7op4a6t5bsThjhvjnhZQ,11682
19
+ python_code_validator-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ python_code_validator-0.1.1.dist-info/entry_points.txt,sha256=pw_HijiZyPxokVJHStTkGCwheTjukDomdk81JyHzv74,66
21
+ python_code_validator-0.1.1.dist-info/top_level.txt,sha256=yowMDfABI5oqgW3hhTdec_7UHGeprkvc2BnqRzNbI5w,15
22
+ python_code_validator-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ validate-code = code_validator.cli:run_from_cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ivan Kovach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ code_validator