python-code-validator 0.1.3__tar.gz → 0.2.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.
- {python_code_validator-0.1.3/src/python_code_validator.egg-info → python_code_validator-0.2.1}/PKG-INFO +3 -25
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/README.md +2 -2
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/pyproject.toml +9 -8
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/__init__.py +1 -1
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/cli.py +27 -17
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/factories.py +31 -13
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/config.py +3 -2
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/core.py +63 -33
- python_code_validator-0.2.1/src/code_validator/output.py +236 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/basic_rules.py +12 -7
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/constraint_logic.py +301 -292
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/selector_nodes.py +9 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1/src/python_code_validator.egg-info}/PKG-INFO +3 -25
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/tests/test_validator.py +9 -9
- python_code_validator-0.1.3/src/code_validator/output.py +0 -90
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/LICENSE +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/setup.cfg +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/__main__.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/__init__.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/ast_utils.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/definitions.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/scope_handler.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/exceptions.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/__init__.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/SOURCES.txt +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/dependency_links.txt +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/entry_points.txt +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/requires.txt +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/top_level.txt +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/tests/test_components.py +0 -0
- {python_code_validator-0.1.3 → python_code_validator-0.2.1}/tests/test_scope_handler.py +0 -0
@@ -1,30 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: python-code-validator
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: A flexible, AST-based framework for static validation of Python code using declarative JSON rules.
|
5
5
|
Author-email: Qu1nel <covach.qn@gmail.com>
|
6
|
-
License: MIT
|
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
|
-
|
6
|
+
License-Expression: MIT
|
28
7
|
Project-URL: Homepage, https://github.com/Qu1nel/PythonCodeValidator
|
29
8
|
Project-URL: Documentation, https://pythoncodevalidator.readthedocs.io/en/latest/
|
30
9
|
Project-URL: Bug Tracker, https://github.com/Qu1nel/PythonCodeValidator/issues
|
@@ -35,7 +14,6 @@ Classifier: Intended Audience :: Education
|
|
35
14
|
Classifier: Programming Language :: Python :: 3
|
36
15
|
Classifier: Programming Language :: Python :: 3.11
|
37
16
|
Classifier: Programming Language :: Python :: 3.12
|
38
|
-
Classifier: License :: OSI Approved :: MIT License
|
39
17
|
Classifier: Operating System :: OS Independent
|
40
18
|
Classifier: Topic :: Software Development :: Quality Assurance
|
41
19
|
Classifier: Topic :: Software Development :: Testing
|
@@ -287,7 +265,7 @@ Validation failed.
|
|
287
265
|
**[Read the Docs](https://[your-project].readthedocs.io)**.
|
288
266
|
- **Developer's Guide**: For a deep dive into the architecture, see the
|
289
267
|
**[How It Works guide](./docs/how_it_works/index.md)**.
|
290
|
-
- **Interactive AI-Powered Docs**:
|
268
|
+
- **Interactive AI-Powered Docs**: **[DeepWiki](https://deepwiki.com/Qu1nel/PythonCodeValidator)**.
|
291
269
|
|
292
270
|
## 🤝 Contributing
|
293
271
|
|
@@ -229,7 +229,7 @@ Validation failed.
|
|
229
229
|
**[Read the Docs](https://[your-project].readthedocs.io)**.
|
230
230
|
- **Developer's Guide**: For a deep dive into the architecture, see the
|
231
231
|
**[How It Works guide](./docs/how_it_works/index.md)**.
|
232
|
-
- **Interactive AI-Powered Docs**:
|
232
|
+
- **Interactive AI-Powered Docs**: **[DeepWiki](https://deepwiki.com/Qu1nel/PythonCodeValidator)**.
|
233
233
|
|
234
234
|
## 🤝 Contributing
|
235
235
|
|
@@ -253,4 +253,4 @@ Email: **[covach.qn@gmail.com](mailto:covach.qn@gmail.com)** Telegram: **[@qnlln
|
|
253
253
|
|
254
254
|
<br/>
|
255
255
|
|
256
|
-
<p align="right"><a href="./LICENSE">MIT</a> © <a href="https://github.com/Qu1nel/">Ivan Kovach</a></p>
|
256
|
+
<p align="right"><a href="./LICENSE">MIT</a> © <a href="https://github.com/Qu1nel/">Ivan Kovach</a></p>
|
@@ -10,13 +10,14 @@ build-backend = "setuptools.build_meta"
|
|
10
10
|
# ==============================================================================
|
11
11
|
[project]
|
12
12
|
name = "python-code-validator"
|
13
|
-
version = "0.1
|
14
|
-
authors = [{ name = "Qu1nel", email = "covach.qn@gmail.com" }]
|
13
|
+
version = "0.2.1"
|
15
14
|
description = "A flexible, AST-based framework for static validation of Python code using declarative JSON rules."
|
15
|
+
keywords = ["validation", "linter", "static analysis", "testing", "education", "ast"]
|
16
|
+
authors = [{ name = "Qu1nel", email = "covach.qn@gmail.com" }]
|
16
17
|
readme = "README.md"
|
17
18
|
requires-python = ">=3.11"
|
18
|
-
license =
|
19
|
-
|
19
|
+
license-files = ["LICEN[CS]E*"]
|
20
|
+
license = "MIT"
|
20
21
|
classifiers = [
|
21
22
|
"Development Status :: 4 - Beta",
|
22
23
|
"Intended Audience :: Developers",
|
@@ -24,7 +25,6 @@ classifiers = [
|
|
24
25
|
"Programming Language :: Python :: 3",
|
25
26
|
"Programming Language :: Python :: 3.11",
|
26
27
|
"Programming Language :: Python :: 3.12",
|
27
|
-
"License :: OSI Approved :: MIT License",
|
28
28
|
"Operating System :: OS Independent",
|
29
29
|
"Topic :: Software Development :: Quality Assurance",
|
30
30
|
"Topic :: Software Development :: Testing",
|
@@ -40,9 +40,11 @@ dependencies = [
|
|
40
40
|
"Documentation" = "https://pythoncodevalidator.readthedocs.io/en/latest/"
|
41
41
|
"Bug Tracker" = "https://github.com/Qu1nel/PythonCodeValidator/issues"
|
42
42
|
|
43
|
+
|
43
44
|
[project.scripts]
|
44
45
|
validate-code = "code_validator.cli:run_from_cli"
|
45
46
|
|
47
|
+
|
46
48
|
[project.optional-dependencies]
|
47
49
|
dev = [
|
48
50
|
"ruff>=0.4.0",
|
@@ -81,8 +83,7 @@ select = [
|
|
81
83
|
"C4", # flake8-comprehensions
|
82
84
|
"D", # pydocstyle
|
83
85
|
]
|
84
|
-
ignore = [
|
85
|
-
]
|
86
|
+
ignore = []
|
86
87
|
|
87
88
|
[tool.ruff.lint.per-file-ignores]
|
88
89
|
"tests/*" = [
|
@@ -95,7 +96,7 @@ ignore = [
|
|
95
96
|
]
|
96
97
|
"src/code_validator/components/__init__.py" = ["D104"]
|
97
98
|
"src/code_validator/rules_library/__init__.py" = ["D104"]
|
98
|
-
|
99
|
+
"src/code_validator/components/factories.py" = ["D107"]
|
99
100
|
|
100
101
|
[tool.ruff.lint.pydocstyle]
|
101
102
|
convention = "google"
|
@@ -35,19 +35,22 @@ def setup_arg_parser() -> argparse.ArgumentParser:
|
|
35
35
|
prog="validate-code",
|
36
36
|
description="Validates a Python source file against a set of JSON rules.",
|
37
37
|
)
|
38
|
+
|
38
39
|
parser.add_argument("solution_path", type=Path, help="Path to the Python solution file to validate.")
|
39
40
|
parser.add_argument("rules_path", type=Path, help="Path to the JSON file with validation rules.")
|
41
|
+
|
40
42
|
parser.add_argument(
|
41
|
-
"
|
42
|
-
"--log-level",
|
43
|
+
"--log",
|
43
44
|
type=LogLevel,
|
44
|
-
|
45
|
-
|
46
|
-
help="Set the logging level (default: WARNING).",
|
45
|
+
default=LogLevel.ERROR,
|
46
|
+
help=("Set the logging level for stderr (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL). Default: ERROR."),
|
47
47
|
)
|
48
|
-
parser.add_argument(
|
48
|
+
parser.add_argument(
|
49
|
+
"--quiet", action="store_true", help="Suppress all stdout output (validation errors and final verdict)."
|
50
|
+
)
|
51
|
+
parser.add_argument("--no-verdict", action="store_true", help="Suppress stdout output verdict, show failed rules.")
|
49
52
|
parser.add_argument("--stop-on-first-fail", action="store_true", help="Stop after the first failed rule.")
|
50
|
-
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
53
|
+
parser.add_argument("--version", "-v", action="version", version=f"%(prog)s {__version__}")
|
51
54
|
return parser
|
52
55
|
|
53
56
|
|
@@ -68,35 +71,42 @@ def run_from_cli() -> None:
|
|
68
71
|
parser = setup_arg_parser()
|
69
72
|
args = parser.parse_args()
|
70
73
|
|
71
|
-
logger = setup_logging(args.
|
72
|
-
console = Console(logger,
|
74
|
+
logger = setup_logging(args.log)
|
75
|
+
console = Console(logger, is_quiet=args.quiet, show_verdict=not args.no_verdict)
|
76
|
+
console.print(f"Level of logging: {args.log}", level=LogLevel.DEBUG)
|
73
77
|
config = AppConfig(
|
74
78
|
solution_path=args.solution_path,
|
75
79
|
rules_path=args.rules_path,
|
76
|
-
log_level=args.
|
77
|
-
|
80
|
+
log_level=args.log,
|
81
|
+
is_quiet=args.quiet,
|
78
82
|
stop_on_first_fail=args.stop_on_first_fail,
|
79
83
|
)
|
84
|
+
console.print(f"Config is: {config}", level=LogLevel.TRACE)
|
80
85
|
|
81
86
|
try:
|
82
87
|
console.print(f"Starting validation for: {config.solution_path}", level=LogLevel.INFO)
|
83
88
|
validator = StaticValidator(config, console)
|
89
|
+
|
90
|
+
console.print("Start of validation..", level=LogLevel.TRACE)
|
84
91
|
is_valid = validator.run()
|
92
|
+
console.print(f"End of validation with result: {is_valid = }", level=LogLevel.TRACE)
|
85
93
|
|
86
94
|
if is_valid:
|
87
|
-
console.print("Validation successful.", level=LogLevel.INFO)
|
95
|
+
console.print("Validation successful.", level=LogLevel.INFO, is_verdict=True)
|
88
96
|
sys.exit(ExitCode.SUCCESS)
|
89
97
|
else:
|
90
|
-
console.print("Validation failed.", level=LogLevel.
|
98
|
+
console.print("Validation failed.", level=LogLevel.INFO, is_verdict=True)
|
91
99
|
sys.exit(ExitCode.VALIDATION_FAILED)
|
92
100
|
|
93
101
|
except CodeValidatorError as e:
|
94
|
-
console.print(
|
102
|
+
console.print("Error: Internal Error of validator!", level=LogLevel.CRITICAL)
|
103
|
+
logger.exception(f"Traceback for CodeValidatorError: {e}")
|
95
104
|
sys.exit(ExitCode.VALIDATION_FAILED)
|
96
105
|
except FileNotFoundError as e:
|
97
|
-
console.print(f"Error: File not found - {e.
|
106
|
+
console.print(f"Error: File not found - {e.filename}!", level=LogLevel.CRITICAL)
|
107
|
+
logger.exception(f"Traceback for FileNotFoundError: {e}")
|
98
108
|
sys.exit(ExitCode.FILE_NOT_FOUND)
|
99
109
|
except Exception as e:
|
100
|
-
console.print(f"An unexpected error occurred: {e}", level=LogLevel.CRITICAL)
|
101
|
-
logger.exception("Traceback for unexpected error:")
|
110
|
+
console.print(f"An unexpected error occurred: {e.__class__.__name__}!", level=LogLevel.CRITICAL)
|
111
|
+
logger.exception(f"Traceback for unexpected error: {e}")
|
102
112
|
sys.exit(ExitCode.UNEXPECTED_ERROR)
|
@@ -10,9 +10,9 @@ rules file and instantiating the appropriate handler classes from the
|
|
10
10
|
import dataclasses
|
11
11
|
from typing import Any, Type, TypeVar
|
12
12
|
|
13
|
-
from ..config import ConstraintConfig, FullRuleCheck, FullRuleConfig, SelectorConfig, ShortRuleConfig
|
13
|
+
from ..config import ConstraintConfig, FullRuleCheck, FullRuleConfig, LogLevel, SelectorConfig, ShortRuleConfig
|
14
14
|
from ..exceptions import RuleParsingError
|
15
|
-
from ..output import Console
|
15
|
+
from ..output import Console, log_initialization
|
16
16
|
from ..rules_library.basic_rules import CheckLinterRule, CheckSyntaxRule, FullRuleHandler
|
17
17
|
from ..rules_library.constraint_logic import (
|
18
18
|
IsForbiddenConstraint,
|
@@ -71,6 +71,7 @@ class RuleFactory:
|
|
71
71
|
_constraint_factory (ConstraintFactory): A factory for creating constraint objects.
|
72
72
|
"""
|
73
73
|
|
74
|
+
@log_initialization(level=LogLevel.TRACE)
|
74
75
|
def __init__(self, console: Console):
|
75
76
|
"""Initializes the RuleFactory.
|
76
77
|
|
@@ -101,8 +102,10 @@ class RuleFactory:
|
|
101
102
|
required keys, or specifies an unknown type.
|
102
103
|
"""
|
103
104
|
rule_id = rule_config.get("rule_id")
|
105
|
+
self._console.print(f"Start parsing rule ({rule_id}):\n{rule_config}", level=LogLevel.TRACE)
|
104
106
|
try:
|
105
107
|
if "type" in rule_config:
|
108
|
+
self._console.print(f"Rule {rule_id} is shorted rule - {rule_config['type']}", level=LogLevel.DEBUG)
|
106
109
|
config = _create_dataclass_from_dict(ShortRuleConfig, rule_config)
|
107
110
|
return self._create_short_rule(config)
|
108
111
|
|
@@ -110,20 +113,31 @@ class RuleFactory:
|
|
110
113
|
raw_selector_cfg = rule_config["check"]["selector"]
|
111
114
|
raw_constraint_cfg = rule_config["check"]["constraint"]
|
112
115
|
|
113
|
-
selector = self._selector_factory.create(raw_selector_cfg)
|
114
|
-
constraint = self._constraint_factory.create(raw_constraint_cfg)
|
115
|
-
|
116
116
|
selector_cfg = _create_dataclass_from_dict(SelectorConfig, raw_selector_cfg)
|
117
117
|
constraint_cfg = _create_dataclass_from_dict(ConstraintConfig, raw_constraint_cfg)
|
118
|
+
|
119
|
+
selector = self._selector_factory.create(selector_cfg)
|
120
|
+
constraint = self._constraint_factory.create(constraint_cfg)
|
121
|
+
|
122
|
+
self._console.print(
|
123
|
+
f"Rule {rule_id} is general rule with: selector - "
|
124
|
+
f"{selector_cfg.type}, constraint - {raw_constraint_cfg['type']}",
|
125
|
+
level=LogLevel.DEBUG,
|
126
|
+
)
|
127
|
+
|
118
128
|
check_cfg = FullRuleCheck(selector=selector_cfg, constraint=constraint_cfg)
|
129
|
+
self._console.print(f"Create FullRuleCheck: {check_cfg}", level=LogLevel.TRACE)
|
130
|
+
|
119
131
|
config = FullRuleConfig(
|
120
132
|
rule_id=rule_config["rule_id"],
|
121
133
|
message=rule_config["message"],
|
122
134
|
check=check_cfg,
|
123
135
|
is_critical=rule_config.get("is_critical", False),
|
124
136
|
)
|
137
|
+
self._console.print(f"Create FullRuleConfig: {config}", level=LogLevel.TRACE)
|
125
138
|
return FullRuleHandler(config, selector, constraint, self._console)
|
126
139
|
else:
|
140
|
+
self._console.print(f"Invalid syntax of rule: {rule_id}", level=LogLevel.WARNING)
|
127
141
|
raise RuleParsingError("Rule must contain 'type' or 'check' key.", rule_id)
|
128
142
|
except (TypeError, KeyError, RuleParsingError) as e:
|
129
143
|
raise RuleParsingError(f"Invalid config for rule '{rule_id}': {e}", rule_id) from e
|
@@ -165,21 +179,23 @@ class SelectorFactory:
|
|
165
179
|
any state.
|
166
180
|
"""
|
167
181
|
|
182
|
+
@log_initialization(level=LogLevel.TRACE)
|
183
|
+
def __init__(self) -> None:
|
184
|
+
pass
|
185
|
+
|
168
186
|
@staticmethod
|
169
|
-
def create(
|
187
|
+
def create(config: SelectorConfig) -> Selector:
|
170
188
|
"""Creates a specific selector instance based on its type.
|
171
189
|
|
172
190
|
This method uses the 'type' field from the selector configuration
|
173
191
|
to determine which concrete Selector class to instantiate.
|
174
192
|
|
175
193
|
Args:
|
176
|
-
|
194
|
+
config: The 'selector' block from a JSON rule.
|
177
195
|
|
178
196
|
Returns:
|
179
197
|
An instance of a class that conforms to the Selector protocol.
|
180
198
|
"""
|
181
|
-
config = _create_dataclass_from_dict(SelectorConfig, selector_config)
|
182
|
-
|
183
199
|
match config.type:
|
184
200
|
case "function_def":
|
185
201
|
return FunctionDefSelector(name=config.name, in_scope_config=config.in_scope)
|
@@ -210,21 +226,23 @@ class ConstraintFactory:
|
|
210
226
|
to a list of AST nodes. This class uses a static `create` method.
|
211
227
|
"""
|
212
228
|
|
229
|
+
@log_initialization(level=LogLevel.TRACE)
|
230
|
+
def __init__(self) -> None:
|
231
|
+
pass
|
232
|
+
|
213
233
|
@staticmethod
|
214
|
-
def create(
|
234
|
+
def create(config: ConstraintConfig) -> Constraint:
|
215
235
|
"""Creates a specific constraint instance based on its type.
|
216
236
|
|
217
237
|
This method uses the 'type' field from the constraint configuration
|
218
238
|
to determine which concrete Constraint class to instantiate.
|
219
239
|
|
220
240
|
Args:
|
221
|
-
|
241
|
+
config: The 'constraint' block from a JSON rule.
|
222
242
|
|
223
243
|
Returns:
|
224
244
|
An instance of a class that conforms to the Constraint protocol.
|
225
245
|
"""
|
226
|
-
config = _create_dataclass_from_dict(ConstraintConfig, constraint_config)
|
227
|
-
|
228
246
|
match config.type:
|
229
247
|
case "is_required":
|
230
248
|
return IsRequiredConstraint(count=config.count)
|
@@ -25,6 +25,7 @@ class ExitCode(IntEnum):
|
|
25
25
|
class LogLevel(StrEnum):
|
26
26
|
"""Defines the supported logging levels for the application."""
|
27
27
|
|
28
|
+
TRACE = "TRACE"
|
28
29
|
DEBUG = "DEBUG"
|
29
30
|
INFO = "INFO"
|
30
31
|
WARNING = "WARNING"
|
@@ -40,14 +41,14 @@ class AppConfig:
|
|
40
41
|
solution_path: The file path to the Python solution to be validated.
|
41
42
|
rules_path: The file path to the JSON rules file.
|
42
43
|
log_level: The minimum logging level for console output.
|
43
|
-
|
44
|
+
is_quiet: If True, suppresses all non-log output to stdout.
|
44
45
|
stop_on_first_fail: If True, halts validation after the first failed rule.
|
45
46
|
"""
|
46
47
|
|
47
48
|
solution_path: Path
|
48
49
|
rules_path: Path
|
49
50
|
log_level: LogLevel
|
50
|
-
|
51
|
+
is_quiet: bool
|
51
52
|
stop_on_first_fail: bool
|
52
53
|
|
53
54
|
|
@@ -45,9 +45,9 @@ import json
|
|
45
45
|
from .components.ast_utils import enrich_ast_with_parents
|
46
46
|
from .components.definitions import Rule
|
47
47
|
from .components.factories import RuleFactory
|
48
|
-
from .config import AppConfig, LogLevel
|
48
|
+
from .config import AppConfig, LogLevel, ShortRuleConfig
|
49
49
|
from .exceptions import RuleParsingError
|
50
|
-
from .output import Console
|
50
|
+
from .output import Console, log_initialization
|
51
51
|
|
52
52
|
|
53
53
|
class StaticValidator:
|
@@ -67,6 +67,7 @@ class StaticValidator:
|
|
67
67
|
_failed_rules (list[int]): A list of rule IDs that failed during the run.
|
68
68
|
"""
|
69
69
|
|
70
|
+
@log_initialization(level=LogLevel.DEBUG)
|
70
71
|
def __init__(self, config: AppConfig, console: Console):
|
71
72
|
"""Initializes the StaticValidator.
|
72
73
|
|
@@ -77,6 +78,7 @@ class StaticValidator:
|
|
77
78
|
"""
|
78
79
|
self._config = config
|
79
80
|
self._console = console
|
81
|
+
|
80
82
|
self._rule_factory = RuleFactory(self._console)
|
81
83
|
self._source_code: str = ""
|
82
84
|
self._ast_tree: ast.Module | None = None
|
@@ -95,39 +97,17 @@ class StaticValidator:
|
|
95
97
|
FileNotFoundError: If the source file specified in the config does not exist.
|
96
98
|
RuleParsingError: If the source file cannot be read for any other reason.
|
97
99
|
"""
|
98
|
-
self._console.print(f"Reading source file: {self._config.solution_path}")
|
100
|
+
self._console.print(f"Reading source file: {self._config.solution_path}", level=LogLevel.DEBUG)
|
99
101
|
try:
|
100
102
|
self._source_code = self._config.solution_path.read_text(encoding="utf-8")
|
103
|
+
self._console.print(f"Source code:\n{self._source_code}\n", level=LogLevel.TRACE)
|
101
104
|
except FileNotFoundError:
|
105
|
+
self._console.print("During reading source file raised FileNotFound", level=LogLevel.TRACE)
|
102
106
|
raise
|
103
107
|
except Exception as e:
|
108
|
+
self._console.print("During reading source file raised some exception..", level=LogLevel.TRACE)
|
104
109
|
raise RuleParsingError(f"Cannot read source file: {e}") from e
|
105
110
|
|
106
|
-
def _parse_ast_tree(self) -> bool:
|
107
|
-
"""Parses the loaded source code into an AST and enriches it.
|
108
|
-
|
109
|
-
This method attempts to parse the source code. If successful, it calls
|
110
|
-
a helper to add parent references to each node in the tree, which is
|
111
|
-
crucial for many advanced checks. If a `SyntaxError` occurs, it
|
112
|
-
checks if a `check_syntax` rule was defined to provide a custom message.
|
113
|
-
|
114
|
-
Returns:
|
115
|
-
bool: True if parsing was successful, False otherwise.
|
116
|
-
"""
|
117
|
-
self._console.print("Parsing Abstract Syntax Tree (AST)...")
|
118
|
-
try:
|
119
|
-
self._ast_tree = ast.parse(self._source_code)
|
120
|
-
enrich_ast_with_parents(self._ast_tree)
|
121
|
-
return True
|
122
|
-
except SyntaxError as e:
|
123
|
-
for rule in self._rules:
|
124
|
-
if getattr(rule.config, "type", None) == "check_syntax":
|
125
|
-
self._console.print(rule.config.message, level=LogLevel.ERROR)
|
126
|
-
self._failed_rules.append(rule.config.rule_id)
|
127
|
-
return False
|
128
|
-
self._console.print(f"Syntax Error found: {e}", level=LogLevel.ERROR)
|
129
|
-
return False
|
130
|
-
|
131
111
|
def _load_and_parse_rules(self) -> None:
|
132
112
|
"""Loads and parses the JSON file into executable Rule objects.
|
133
113
|
|
@@ -140,20 +120,52 @@ class StaticValidator:
|
|
140
120
|
RuleParsingError: If the JSON is malformed or a rule configuration
|
141
121
|
is invalid.
|
142
122
|
"""
|
143
|
-
self._console.print(f"Loading rules from: {self._config.rules_path}")
|
123
|
+
self._console.print(f"Loading rules from: {self._config.rules_path}", level=LogLevel.DEBUG)
|
144
124
|
try:
|
145
125
|
rules_data = json.loads(self._config.rules_path.read_text(encoding="utf-8"))
|
126
|
+
self._console.print(f"Load rules:\n{rules_data}", level=LogLevel.TRACE)
|
146
127
|
raw_rules = rules_data.get("validation_rules")
|
147
128
|
if not isinstance(raw_rules, list):
|
148
129
|
raise RuleParsingError("`validation_rules` key not found or is not a list.")
|
149
130
|
|
131
|
+
self._console.print(f"Found {len(raw_rules)}.", level=LogLevel.DEBUG)
|
150
132
|
self._rules = [self._rule_factory.create(rule) for rule in raw_rules]
|
151
|
-
self._console.print(f"Successfully parsed {len(self._rules)} rules.")
|
133
|
+
self._console.print(f"Successfully parsed {len(self._rules)} rules.", level=LogLevel.DEBUG)
|
152
134
|
except json.JSONDecodeError as e:
|
135
|
+
self._console.print("During reading file of rules raised JsonDecodeError..", level=LogLevel.TRACE)
|
153
136
|
raise RuleParsingError(f"Invalid JSON in rules file: {e}") from e
|
154
137
|
except FileNotFoundError:
|
138
|
+
self._console.print("During reading file of rules raised FileNotFound", level=LogLevel.TRACE)
|
155
139
|
raise
|
156
140
|
|
141
|
+
def _parse_ast_tree(self) -> bool:
|
142
|
+
"""Parses the loaded source code into an AST and enriches it.
|
143
|
+
|
144
|
+
This method attempts to parse the source code. If successful, it calls
|
145
|
+
a helper to add parent references to each node in the tree, which is
|
146
|
+
crucial for many advanced checks. If a `SyntaxError` occurs, it
|
147
|
+
checks if a `check_syntax` rule was defined to provide a custom message.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
bool: True if parsing was successful, False otherwise.
|
151
|
+
"""
|
152
|
+
self._console.print("Parsing Abstract Syntax Tree (AST)...", level=LogLevel.DEBUG)
|
153
|
+
try:
|
154
|
+
self._console.print("Start parse source code.", level=LogLevel.TRACE)
|
155
|
+
self._ast_tree = ast.parse(self._source_code)
|
156
|
+
enrich_ast_with_parents(self._ast_tree)
|
157
|
+
return True
|
158
|
+
except SyntaxError as e:
|
159
|
+
self._console.print("In source code SyntaxError..", level=LogLevel.TRACE)
|
160
|
+
for rule in self._rules:
|
161
|
+
if getattr(rule.config, "type", None) == "check_syntax":
|
162
|
+
self._console.print(rule.config.message, level=LogLevel.ERROR, show_user=True)
|
163
|
+
self._console.print(f"Failed rule id: {rule.config.rule_id}", level=LogLevel.DEBUG)
|
164
|
+
self._failed_rules.append(rule.config.rule_id)
|
165
|
+
return False
|
166
|
+
self._console.print(f"Syntax Error found: {e}", level=LogLevel.ERROR)
|
167
|
+
return False
|
168
|
+
|
157
169
|
def run(self) -> bool:
|
158
170
|
"""Runs the entire validation process from start to finish.
|
159
171
|
|
@@ -174,20 +186,38 @@ class StaticValidator:
|
|
174
186
|
if not self._parse_ast_tree():
|
175
187
|
return False
|
176
188
|
|
177
|
-
|
189
|
+
self._console.print("Lead source code, load and parse rules and parsing code - PASS", level=LogLevel.DEBUG)
|
190
|
+
|
191
|
+
except (FileNotFoundError, RuleParsingError) as e:
|
192
|
+
self._console.print(
|
193
|
+
f"In method `run` of 'StaticValidator' raised exception {e.__class__.__name__}", level=LogLevel.WARNING
|
194
|
+
)
|
178
195
|
raise
|
179
196
|
|
197
|
+
self._console.print("Starting check rules..", level=LogLevel.DEBUG)
|
180
198
|
for rule in self._rules:
|
181
199
|
if getattr(rule.config, "type", None) == "check_syntax":
|
182
200
|
continue
|
183
201
|
|
184
|
-
self._console.print(
|
202
|
+
self._console.print(
|
203
|
+
f"Executing rule: {rule.config.rule_id}"
|
204
|
+
+ (
|
205
|
+
f" [{rule.config.check.selector.type}, {rule.config.check.constraint.type}, "
|
206
|
+
f"is_critical={rule.config.is_critical}]"
|
207
|
+
if not isinstance(rule.config, ShortRuleConfig)
|
208
|
+
else ""
|
209
|
+
),
|
210
|
+
level=LogLevel.INFO,
|
211
|
+
)
|
185
212
|
is_passed = rule.execute(self._ast_tree, self._source_code)
|
186
213
|
if not is_passed:
|
187
|
-
self._console.print(rule.config.message, level=LogLevel.
|
214
|
+
self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
|
215
|
+
self._console.print(f"Rule {rule.config.rule_id} - FAIL", level=LogLevel.INFO)
|
188
216
|
self._failed_rules.append(rule.config.rule_id)
|
189
217
|
if getattr(rule.config, "is_critical", False) or self._config.stop_on_first_fail:
|
190
218
|
self._console.print("Critical rule failed. Halting validation.", level=LogLevel.WARNING)
|
191
219
|
break
|
220
|
+
else:
|
221
|
+
self._console.print(f"Rule {rule.config.rule_id} - PASS", level=LogLevel.INFO)
|
192
222
|
|
193
223
|
return not self._failed_rules
|