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.
Files changed (31) hide show
  1. {python_code_validator-0.1.3/src/python_code_validator.egg-info → python_code_validator-0.2.1}/PKG-INFO +3 -25
  2. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/README.md +2 -2
  3. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/pyproject.toml +9 -8
  4. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/__init__.py +1 -1
  5. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/cli.py +27 -17
  6. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/factories.py +31 -13
  7. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/config.py +3 -2
  8. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/core.py +63 -33
  9. python_code_validator-0.2.1/src/code_validator/output.py +236 -0
  10. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/basic_rules.py +12 -7
  11. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/constraint_logic.py +301 -292
  12. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/selector_nodes.py +9 -0
  13. {python_code_validator-0.1.3 → python_code_validator-0.2.1/src/python_code_validator.egg-info}/PKG-INFO +3 -25
  14. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/tests/test_validator.py +9 -9
  15. python_code_validator-0.1.3/src/code_validator/output.py +0 -90
  16. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/LICENSE +0 -0
  17. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/setup.cfg +0 -0
  18. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/__main__.py +0 -0
  19. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/__init__.py +0 -0
  20. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/ast_utils.py +0 -0
  21. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/definitions.py +0 -0
  22. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/components/scope_handler.py +0 -0
  23. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/exceptions.py +0 -0
  24. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/code_validator/rules_library/__init__.py +0 -0
  25. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/SOURCES.txt +0 -0
  26. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/dependency_links.txt +0 -0
  27. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/entry_points.txt +0 -0
  28. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/requires.txt +0 -0
  29. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/src/python_code_validator.egg-info/top_level.txt +0 -0
  30. {python_code_validator-0.1.3 → python_code_validator-0.2.1}/tests/test_components.py +0 -0
  31. {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
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 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
-
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**: *(Coming Soon)* An interactive documentation experience.
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**: *(Coming Soon)* An interactive documentation experience.
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.3"
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 = { file = "LICENSE" }
19
- keywords = ["validation", "linter", "static analysis", "testing", "education", "ast"]
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"
@@ -54,4 +54,4 @@ __all__ = [
54
54
  "RuleParsingError",
55
55
  ]
56
56
 
57
- __version__ = "0.1.3"
57
+ __version__ = "0.2.1"
@@ -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
- "-l",
42
- "--log-level",
43
+ "--log",
43
44
  type=LogLevel,
44
- choices=list(LogLevel),
45
- default=LogLevel.WARNING,
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("--silent", action="store_true", help="Suppress stdout output, show only logs.")
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.log_level)
72
- console = Console(logger, is_silent=args.silent)
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.log_level,
77
- is_silent=args.silent,
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.ERROR)
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(str(e), level=LogLevel.CRITICAL)
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.strerror}: {e.filename}", level=LogLevel.CRITICAL)
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(selector_config: dict[str, Any]) -> Selector:
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
- selector_config: The 'selector' block from a JSON rule.
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(constraint_config: dict[str, Any]) -> Constraint:
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
- constraint_config: The 'constraint' block from a JSON rule.
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
- is_silent: If True, suppresses all non-log output to stdout.
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
- is_silent: bool
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
- except (FileNotFoundError, RuleParsingError):
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(f"Executing rule: {rule.config.rule_id}", level=LogLevel.DEBUG)
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.ERROR)
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