qgis-plugin-analyzer 1.4.0__py3-none-any.whl → 1.6.0__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.
Files changed (41) hide show
  1. analyzer/__init__.py +2 -1
  2. analyzer/cli/__init__.py +14 -0
  3. analyzer/cli/app.py +147 -0
  4. analyzer/cli/base.py +93 -0
  5. analyzer/cli/commands/__init__.py +19 -0
  6. analyzer/cli/commands/analyze.py +47 -0
  7. analyzer/cli/commands/fix.py +58 -0
  8. analyzer/cli/commands/init.py +41 -0
  9. analyzer/cli/commands/list_rules.py +41 -0
  10. analyzer/cli/commands/security.py +46 -0
  11. analyzer/cli/commands/summary.py +52 -0
  12. analyzer/cli/commands/version.py +41 -0
  13. analyzer/cli.py +4 -281
  14. analyzer/commands.py +163 -0
  15. analyzer/engine.py +491 -245
  16. analyzer/fixer.py +206 -130
  17. analyzer/reporters/markdown_reporter.py +88 -14
  18. analyzer/reporters/summary_reporter.py +226 -49
  19. analyzer/rules/qgis_rules.py +3 -1
  20. analyzer/scanner.py +219 -711
  21. analyzer/secrets.py +84 -0
  22. analyzer/security_checker.py +85 -0
  23. analyzer/security_rules.py +127 -0
  24. analyzer/transformers.py +29 -8
  25. analyzer/utils/__init__.py +2 -0
  26. analyzer/utils/path_utils.py +53 -1
  27. analyzer/validators.py +90 -55
  28. analyzer/visitors/__init__.py +19 -0
  29. analyzer/visitors/base.py +75 -0
  30. analyzer/visitors/composite_visitor.py +73 -0
  31. analyzer/visitors/imports_visitor.py +85 -0
  32. analyzer/visitors/metrics_visitor.py +158 -0
  33. analyzer/visitors/security_visitor.py +52 -0
  34. analyzer/visitors/standards_visitor.py +284 -0
  35. {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/METADATA +32 -10
  36. qgis_plugin_analyzer-1.6.0.dist-info/RECORD +52 -0
  37. {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/WHEEL +1 -1
  38. qgis_plugin_analyzer-1.4.0.dist-info/RECORD +0 -30
  39. {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/entry_points.txt +0 -0
  40. {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/licenses/LICENSE +0 -0
  41. {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ """AST visitor for QGIS-specific standards and best practices."""
2
+
3
+ import ast
4
+ from typing import Any, Dict, List, Optional, Set
5
+
6
+ from ..rules.qgis_rules import I18N_METHODS
7
+ from .base import BaseVisitor
8
+
9
+
10
+ class StandardsVisitor(BaseVisitor):
11
+ """Visitor focused on QGIS-specific standards and best practices.
12
+
13
+ Detects issues like:
14
+ - Missing i18n translations
15
+ - Missing signal slots
16
+ - Mandatory cleanup methods
17
+ - Obsolete API usage
18
+ - Blocking network calls in UI
19
+ - Spatial index optimization opportunities
20
+ - Non-pythonic loops
21
+ """
22
+
23
+ def __init__(self, rel_path: str, rules_config: Optional[Dict[str, Any]] = None) -> None:
24
+ """Initializes the standards visitor.
25
+
26
+ Args:
27
+ rel_path: Relative path to the file being analyzed.
28
+ rules_config: Optional configuration for audit rules and severities.
29
+ """
30
+ super().__init__(rel_path, rules_config)
31
+ self.class_methods_stack: List[Set[str]] = []
32
+ self.i18n_methods = I18N_METHODS
33
+
34
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
35
+ """Analyzes class definitions.
36
+
37
+ Args:
38
+ node: The class definition AST node.
39
+ """
40
+ methods = {
41
+ item.name
42
+ for item in node.body
43
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef))
44
+ }
45
+ self.class_methods_stack.append(methods)
46
+
47
+ # MANDATORY_CLEANUP
48
+ has_init_gui = "initGui" in methods
49
+ has_unload = "unload" in methods
50
+
51
+ if has_init_gui and not has_unload:
52
+ self._report_issue(
53
+ "MANDATORY_CLEANUP",
54
+ node.lineno,
55
+ f"Class '{node.name}' implements 'initGui()' but is missing 'unload()'.",
56
+ f"class {node.name}...",
57
+ )
58
+
59
+ self.generic_visit(node)
60
+ self.class_methods_stack.pop()
61
+
62
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
63
+ """Analyzes function definitions.
64
+
65
+ Args:
66
+ node: The function definition AST node.
67
+ """
68
+ # IFACE_AS_ARGUMENT
69
+ for arg in node.args.args:
70
+ if arg.annotation and isinstance(arg.annotation, ast.Name):
71
+ if arg.annotation.id == "QgisInterface":
72
+ self._report_issue(
73
+ "IFACE_AS_ARGUMENT",
74
+ node.lineno,
75
+ f"Function '{node.name}' receives 'QgisInterface' as an argument. Use the global 'iface' or Singleton pattern.",
76
+ ast.unparse(node).split("\n")[0],
77
+ )
78
+
79
+ self.generic_visit(node)
80
+
81
+ def visit_Call(self, node: ast.Call) -> None:
82
+ """Analyzes function calls.
83
+
84
+ Args:
85
+ node: The call AST node.
86
+ """
87
+ self._check_obsolete_api(node)
88
+ self._check_missing_i18n(node)
89
+ self._check_missing_slot(node)
90
+ self._check_unsafe_subprocess(node)
91
+ self._check_blocking_network(node)
92
+ self.generic_visit(node)
93
+
94
+ def visit_For(self, node: ast.For) -> None:
95
+ """Analyzes loop nodes.
96
+
97
+ Args:
98
+ node: The for-loop AST node.
99
+ """
100
+ # SPATIAL_INDEX check
101
+ if (
102
+ isinstance(node.iter, ast.Call)
103
+ and isinstance(node.iter.func, ast.Attribute)
104
+ and node.iter.func.attr == "getFeatures"
105
+ ):
106
+ warn = False
107
+ if not node.iter.args:
108
+ warn = True
109
+ elif len(node.iter.args) == 1:
110
+ arg = node.iter.args[0]
111
+ if (
112
+ isinstance(arg, ast.Call)
113
+ and isinstance(arg.func, ast.Name)
114
+ and arg.func.id == "QgsFeatureRequest"
115
+ ):
116
+ if not arg.args and not arg.keywords:
117
+ warn = True
118
+
119
+ if warn:
120
+ self._report_issue(
121
+ "SPATIAL_INDEX",
122
+ node.lineno,
123
+ "Iteration over features with getFeatures() and no filter.",
124
+ ast.unparse(node.iter),
125
+ )
126
+
127
+ # NON_PYTHONIC_LOOP
128
+ for body_node in ast.walk(node):
129
+ if isinstance(body_node, ast.AugAssign) and isinstance(body_node.op, ast.Add):
130
+ if (
131
+ isinstance(body_node.target, ast.Name)
132
+ and isinstance(body_node.value, ast.Constant)
133
+ and body_node.value.value == 1
134
+ ):
135
+ self._report_issue(
136
+ "NON_PYTHONIC_LOOP",
137
+ body_node.lineno,
138
+ f"Manual counter '{body_node.target.id} += 1' detected inside loop.",
139
+ ast.unparse(body_node),
140
+ )
141
+
142
+ self.generic_visit(node)
143
+
144
+ def _check_obsolete_api(self, node: ast.Call) -> None:
145
+ """Checks for obsolete API usage.
146
+
147
+ Args:
148
+ node: The call AST node.
149
+ """
150
+ if isinstance(node.func, ast.Attribute) and node.func.attr == "writeAsVectorFormat":
151
+ self._report_issue(
152
+ "OBSOLETE_API",
153
+ node.lineno,
154
+ "Obsolete writeAsVectorFormat() usage. Use writeAsVectorFormatV3().",
155
+ ast.unparse(node),
156
+ )
157
+
158
+ def _check_missing_i18n(self, node: ast.Call) -> None:
159
+ """Checks for missing i18n translations.
160
+
161
+ Args:
162
+ node: The call AST node.
163
+ """
164
+ if isinstance(node.func, ast.Attribute) and node.func.attr in self.i18n_methods:
165
+ if (
166
+ node.args
167
+ and isinstance(node.args[0], ast.Constant)
168
+ and isinstance(node.args[0].value, str)
169
+ ):
170
+ val = node.args[0].value
171
+ if val.strip() and not val.startswith("%"):
172
+ self._report_issue(
173
+ "MISSING_I18N",
174
+ node.lineno,
175
+ f"Untranslated UI text string in '{node.func.attr}': '{val}'. Use self.tr().",
176
+ ast.unparse(node),
177
+ )
178
+
179
+ def _check_missing_slot(self, node: ast.Call) -> None:
180
+ """Checks for potentially missing signal slots.
181
+
182
+ Args:
183
+ node: The call AST node.
184
+ """
185
+ if isinstance(node.func, ast.Attribute) and node.func.attr == "connect" and node.args:
186
+ arg = node.args[0]
187
+ if (
188
+ isinstance(arg, ast.Attribute)
189
+ and isinstance(arg.value, ast.Name)
190
+ and arg.value.id == "self"
191
+ ):
192
+ slot = arg.attr
193
+ if self.class_methods_stack and slot not in self.class_methods_stack[-1]:
194
+ self._report_issue(
195
+ "POTENTIAL_MISSING_SLOT",
196
+ node.lineno,
197
+ f"Connected slot 'self.{slot}' not found in class definitions.",
198
+ )
199
+
200
+ def _check_unsafe_subprocess(self, node: ast.Call) -> None:
201
+ """Checks for unsafe subprocess usage.
202
+
203
+ Args:
204
+ node: The call AST node.
205
+ """
206
+ is_subprocess = False
207
+ if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
208
+ if node.func.value.id == "subprocess" and node.func.attr in {
209
+ "run",
210
+ "call",
211
+ "Popen",
212
+ "check_call",
213
+ "check_output",
214
+ }:
215
+ is_subprocess = True
216
+
217
+ if not is_subprocess:
218
+ return
219
+
220
+ shell_true = False
221
+ for kw in node.keywords:
222
+ if kw.arg == "shell" and isinstance(kw.value, ast.Constant) and kw.value.value is True:
223
+ shell_true = True
224
+ break
225
+
226
+ if shell_true:
227
+ self._report_issue(
228
+ "UNSAFE_SUBPROCESS",
229
+ node.lineno,
230
+ "Subprocess called with 'shell=True'.",
231
+ ast.unparse(node),
232
+ )
233
+ return
234
+
235
+ if node.args:
236
+ cmd_arg = node.args[0]
237
+ if isinstance(cmd_arg, (ast.JoinedStr, ast.BinOp)):
238
+ self._report_issue(
239
+ "UNSAFE_SUBPROCESS",
240
+ node.lineno,
241
+ "Possible unquoted variable injection.",
242
+ ast.unparse(node),
243
+ )
244
+
245
+ def _check_blocking_network(self, node: ast.Call) -> None:
246
+ """Checks for blocking network calls in UI files.
247
+
248
+ Args:
249
+ node: The call AST node.
250
+ """
251
+ is_network = False
252
+ if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
253
+ if node.func.value.id == "requests" and node.func.attr in {
254
+ "get",
255
+ "post",
256
+ "put",
257
+ "delete",
258
+ "patch",
259
+ }:
260
+ is_network = True
261
+
262
+ if not is_network:
263
+ # Heuristic for urlopen
264
+ attr_chain = []
265
+ curr = node.func
266
+ while isinstance(curr, ast.Attribute):
267
+ attr_chain.append(curr.attr)
268
+ curr = curr.value
269
+ if isinstance(curr, ast.Name):
270
+ attr_chain.append(curr.id)
271
+ if attr_chain == ["urlopen", "request", "urllib"]:
272
+ is_network = True
273
+
274
+ if is_network:
275
+ is_ui_file = any(
276
+ kw in self.rel_path.lower() for kw in ["gui", "ui", "dialog", "widget"]
277
+ )
278
+ if is_ui_file:
279
+ self._report_issue(
280
+ "BLOCKING_NETWORK_CALL",
281
+ node.lineno,
282
+ "Synchronous network call detected in UI file.",
283
+ ast.unparse(node),
284
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qgis-plugin-analyzer
3
- Version: 1.4.0
3
+ Version: 1.6.0
4
4
  Summary: A professional static analysis tool for QGIS (PyQGIS) plugins
5
5
  Author-email: geociencio <juanbernales@gmail.com>
6
6
  License: GPL-3.0-or-later
@@ -34,13 +34,16 @@ Dynamic: license-file
34
34
  ![License](https://img.shields.io/badge/License-GPLv3-blue.svg)
35
35
  ![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)
36
36
  ![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?logo=git)
37
- ![Quality Score](https://img.shields.io/badge/Module%20Stability-55.6%2F100-yellow)
38
- ![Maintainability](https://img.shields.io/badge/Maintainability-100.0%2F100-brightgreen)
37
+ ![Quality Score](https://img.shields.io/badge/Module%20Stability-92.3%2F100-brightgreen)
38
+ ![Maintainability](https://img.shields.io/badge/Maintainability-84.1%2F100-green)
39
+ ![Security Score](https://img.shields.io/badge/Security--Bandit-98.7%2F100-brightgreen)
39
40
 
40
41
  The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for QGIS (PyQGIS) plugin developers. Its goal is to elevate plugin quality by ensuring they follow community best practices and are optimized for AI-assisted development.
41
42
 
42
43
  ## ✨ Main Features
43
44
 
45
+ - **Security Core (Bandit-inspired)**: Professional vulnerability scanning detecting `eval`, `exec`, shell injections, and SQL injection risks.
46
+ - **Deep Entropy Secret Scanner**: Detects hardcoded API keys, passwords, and sensitive tokens using regex and information entropy.
44
47
  - **High-Performance Engine**: Parallel analysis powered by `ProcessPoolExecutor` for ultra-fast execution on multi-core systems.
45
48
  - **Project Auto-Detection**: Intelligently distinguishes between official QGIS Plugins and Generic Python Projects, tailoring validation logic accordingly.
46
49
  - **Advanced Ignore Engine**: Robust `.analyzerignore` support with non-anchored patterns and smart default excludes (`.venv`, `build`, etc.).
@@ -59,21 +62,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
59
62
 
60
63
  | Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
61
64
  | :--- | :---: | :---: | :---: | :---: |
65
+ | **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
62
66
  | **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
63
67
  | **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
64
68
  | **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
65
69
  | **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
66
- | **Compliance Checks** | ✅ | ❌ | ❌ | ✅ |
67
- | **i18n / API Audit** | ✅ | ❌ | ❌ | ✅ |
68
- | **Architecture Audit** | ✅ (UI/Core) | ❌ | ❌ | ❌ |
70
+ | **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
71
+ | **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
69
72
  | **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
70
73
  | **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
71
74
 
72
75
  ### Key Differentiators
73
76
 
74
- 1. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
75
- 2. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
76
- 3. **Repository Compliance**: Local pre-checks to ensure your plugin passes the Official QGIS Repository policies.
77
+ 1. **Shift Left (Run Locally)**: The biggest advantage is being able to run the **same high-standard checks** as the Official Repository *before* you upload your plugin. No more "reject-fix-upload" loops.
78
+ 2. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
79
+ 3. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
77
80
  4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
78
81
  5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
79
82
 
@@ -129,7 +132,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
129
132
 
130
133
  ```yaml
131
134
  - repo: https://github.com/geociencio/qgis-plugin-analyzer
132
- rev: v1.4.0 # Use the latest tag
135
+ rev: main # Use 'main' for latest features or a specific tag like v1.5.0
133
136
  hooks:
134
137
  - id: qgis-plugin-analyzer
135
138
  ```
@@ -188,6 +191,7 @@ Audits an existing QGIS plugin repository.
188
191
  | :--- | :--- | :--- |
189
192
  | `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
190
193
  | `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
194
+ | `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
191
195
  | `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
192
196
 
193
197
  ### `qgis-analyzer fix`
@@ -210,6 +214,18 @@ Shows a professional, color-coded summary of findings directly in your terminal.
210
214
  | `-b`, `--by` | Granularity of the summary: `total`, `modules`, `functions`, `classes`. | `total` |
211
215
  | `-i`, `--input` | Path to the `project_context.json` file to summarize. | `analysis_results/project_context.json` |
212
216
 
217
+ ### `qgis-analyzer security`
218
+ Performs a focused security scan on a file or directory.
219
+
220
+ | Argument | Description | Default |
221
+ | :--- | :--- | :--- |
222
+ | `path` | **(Required)** Path to the file or directory to scan. | N/A |
223
+ | `--deep` | Run more intensive (but slower) security checks. | `False` |
224
+ | `-p`, `--profile`| Configuration profile. | `default` |
225
+
226
+ ### `qgis-analyzer version`
227
+ Shows the current version of the analyzer.
228
+
213
229
  **Example:**
214
230
  ```bash
215
231
  # Executive summary
@@ -245,6 +261,7 @@ The development of this analyzer is based on official QGIS community guidelines,
245
261
  - **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
246
262
  - **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
247
263
  - **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
264
+ - **[QGIS Security Scanning Documentation](https://plugins.qgis.org/docs/security-scanning)**: Official guide on automated security analysis (Bandit, detect-secrets) for plugins.
248
265
 
249
266
  ### Industry & Community Standards
250
267
  - **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
@@ -254,6 +271,11 @@ The development of this analyzer is based on official QGIS community guidelines,
254
271
  - **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
255
272
  - **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
256
273
 
274
+ ### Security Standards
275
+ - **[Bandit (PyCQA)](https://bandit.readthedocs.io/)**: The security rules implemented (B1xx - B6xx) are directly derived from the Bandit project's rule set for identifying common security issues in Python code.
276
+ - **[CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)**: Security findings are mapped to standard CWE IDs (e.g., CWE-78 Command Injection, CWE-89 SQL Injection) for industry-standard classification.
277
+ - **[OWASP Top 10](https://owasp.org/www-project-top-ten/)**: The "Hardcoded Secret" and "Injection" checks align with critical OWASP vulnerabilities.
278
+
257
279
  ### Internal Resources
258
280
  - **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
259
281
  - **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
@@ -0,0 +1,52 @@
1
+ __init__.py,sha256=m27nXDOFpStOzfg3Qqeaw7yj1wNmpnOH7xk5svlCprA,1185
2
+ analyzer/__init__.py,sha256=4cVdHyVkLCwrgmcijSYhVDiKxMJJFTeK4SOJXNXWJwc,1129
3
+ analyzer/cli.py,sha256=OKsc-fSI7mc7Grfirkt40G2WWYBmy5W6WZytVa6fZ_Q,1389
4
+ analyzer/commands.py,sha256=-qFxiEsBW0nhQ01C4uvpejoSlKMtvg8NOUBxuMKrJ88,5256
5
+ analyzer/engine.py,sha256=Hq3J7ivf4w3_VV2f1hi_YpkibfncGaddnrN582K_goQ,31108
6
+ analyzer/fixer.py,sha256=fnU5nLEfrk2sd5aNftxNWySef2bPLthkz844Znc1mDk,12751
7
+ analyzer/scanner.py,sha256=AzPWJnTAHFrnolqzV5kw56LYqWBKR4vIMXXoiiEUdog,10339
8
+ analyzer/secrets.py,sha256=Ml-hRSFgLlAsi6KeD2V53OuQ1E8qWE_65yzDm_RD3AE,2899
9
+ analyzer/security_checker.py,sha256=YXQeIvtrtwvqHm5xGW8zXNrPycdsLyavCnxFOltf8Rs,2839
10
+ analyzer/security_rules.py,sha256=x95EweqgTOFNcM6In2aA37qOeqsv1PBYkCsRmwhpwGw,5182
11
+ analyzer/semantic.py,sha256=cVmSiTmI-b4DFZ5Q6kZUdqd6ZPH92pJmNAE60MIiZRI,8192
12
+ analyzer/transformers.py,sha256=8uAoYrzB1BeVCTGL4OjGd4RnKje4vxDJkgeVMKWlihA,7673
13
+ analyzer/validators.py,sha256=f8eVnCEACSMCTaLNyRNHpQK1TJIgRHESyhRc_SaJE_c,9423
14
+ analyzer/cli/__init__.py,sha256=_twmhOZ7hqLMGDyXmGYig03ajTxk7yCK6Efe4wEaUP8,238
15
+ analyzer/cli/app.py,sha256=BEuoWN90IjiBJXq01kSuHol9v-lzoUwm_iB1ME0NPHA,4413
16
+ analyzer/cli/base.py,sha256=tFo_MhaO1i3HN2uODHaRVW_AC3mRGDSIxL9o_cEJGeA,2604
17
+ analyzer/cli/commands/__init__.py,sha256=AxhWDUrUdHtvRGC0tgLB5kUOSEKbXsBeFlIVgA7Zca0,439
18
+ analyzer/cli/commands/analyze.py,sha256=6T9HubkqD76OeM5PLtU6-sjn3N7fuDKk74Koe4zIHWc,1218
19
+ analyzer/cli/commands/fix.py,sha256=XM4s7H-CW_EzG-96pDqGKbgYG16i88FGExc1RBhjsXg,1644
20
+ analyzer/cli/commands/init.py,sha256=6zU9iy8xlFlo8yVh-LM5T6H4B5AYOjTgBpwZ5b7M-oE,978
21
+ analyzer/cli/commands/list_rules.py,sha256=UtaWpEsgelw_nSqRwVOmx_hwkeVhpZqFvH24qRDt4LI,997
22
+ analyzer/cli/commands/security.py,sha256=B2RMUZuu-QBjen1Jc6uDwY-007qdPFCsPJUBrS8cDT0,1202
23
+ analyzer/cli/commands/summary.py,sha256=UuQuAl39z898u_OHc75q8IvgORX_rPGXF8bmyH3msJE,1383
24
+ analyzer/cli/commands/version.py,sha256=Ghn9TnEwnDvzqT25qxZihUa3UXvvo8RqAtKF_lLXDnk,997
25
+ analyzer/models/__init__.py,sha256=NlDgdV1i9CoWJBgiENoJQuHnYCOzO6U_ui3wHOeT7xI,160
26
+ analyzer/models/analysis_models.py,sha256=WXsYgbjD4ngBoyaY-YkwMMl2Jpbn96hue6zuSERZRb0,2362
27
+ analyzer/reporters/__init__.py,sha256=fVsu2gCuBdDd_STD97Jku8otcO3j7NZrxxd_pGzf6Sw,280
28
+ analyzer/reporters/html_reporter.py,sha256=j64KkjiRwwRBzIPZE7bBJGsbJEYIoSnZeho63tI4c5A,14919
29
+ analyzer/reporters/markdown_reporter.py,sha256=GNklqb8vJcUCA79EICD4RR8idSLv_pAInQf7KYVnmGQ,10407
30
+ analyzer/reporters/summary_reporter.py,sha256=RwmrQ6b8DOIFe9QUj2G2xx1CdZJsjjWlTgLPepICvU0,12270
31
+ analyzer/rules/__init__.py,sha256=UNZoY89SFQoUKYGj_rjwvv8md6vXSSM2pGPV6MqC2Fg,261
32
+ analyzer/rules/modernization_rules.py,sha256=IJw8c6v7Q_5z3xF9Dh0eBrproRFZFtIOyWcArAsSv18,1071
33
+ analyzer/rules/qgis_rules.py,sha256=vpFkdC3RnlkcSHqJB_Ec1wYLn8qV8xMjXZCgitslRSc,2578
34
+ analyzer/utils/__init__.py,sha256=_7xpeayVXOKp92a_ennR2HF1tKBNgbyQb7M5eyY2O_c,1064
35
+ analyzer/utils/ast_utils.py,sha256=LZ9NLrbJlhFGM7LsX6YNexOi_avJ0Sxzf8GdZHvoXK4,3831
36
+ analyzer/utils/config_utils.py,sha256=reNyUonmik8Tpwx0RbaslsiienIqPDeiXBfh4Pv23_E,4690
37
+ analyzer/utils/logging_utils.py,sha256=V0z7XeF46tj2oI247j2fRRafClbtMDVscVmg2JFYHV4,1257
38
+ analyzer/utils/path_utils.py,sha256=OfX0IwBnMk9fSO6GIU-iyxxPCCalIcEjxPiCh36SphY,5817
39
+ analyzer/utils/performance_utils.py,sha256=M3mfMVrOvyfgQiCQB0R5IkkBnAx-L9er4fGR9UL5E-0,4253
40
+ analyzer/visitors/__init__.py,sha256=icwHq0hFSFYOXYiQV0AE2ev9HE8y8nTs0cGJ5nax20M,527
41
+ analyzer/visitors/base.py,sha256=sDa6C3-1Y4gZ5XT9Svq8-eZhiEXZB3InZvPIHwDd3gI,2513
42
+ analyzer/visitors/composite_visitor.py,sha256=eA9kdG-8-59rRZwLgrUo8uB2yq6yhr3LIas7zHSyDtQ,2694
43
+ analyzer/visitors/imports_visitor.py,sha256=jpthTiycxg7aMuXy-g2zO-sB82GXMfNYNj2pdM0gEbE,2883
44
+ analyzer/visitors/metrics_visitor.py,sha256=xd9SMZjpATkwRcwqbNMBXwXwJNdSap9dUIRPytkDKDg,5439
45
+ analyzer/visitors/security_visitor.py,sha256=ontmVz3Qxto-sk6oNnDKf1IzjBvDVIPm6jn4mI34KQI,1675
46
+ analyzer/visitors/standards_visitor.py,sha256=RGmOMnS-yX_mEm2WTDFDsWY7cnk_agZhTBEk1gERUYE,9789
47
+ qgis_plugin_analyzer-1.6.0.dist-info/licenses/LICENSE,sha256=bMxViCXCE9h3bzmw6oUvy4R_CvzOrV3aYhIvjBPx20A,35091
48
+ qgis_plugin_analyzer-1.6.0.dist-info/METADATA,sha256=nWCMyJycFbXLEKz6Q7tuW5UGVx9LXIl7YRSW7WqrdOo,14757
49
+ qgis_plugin_analyzer-1.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
50
+ qgis_plugin_analyzer-1.6.0.dist-info/entry_points.txt,sha256=1dwiqkZC4hGYExrOgyhZkxv7CbClSZqJkVk42nlw1IY,52
51
+ qgis_plugin_analyzer-1.6.0.dist-info/top_level.txt,sha256=i9U1DboCuTuaYpS-5GcgUeKIlSI00PoxYtnrfQWD8wo,18
52
+ qgis_plugin_analyzer-1.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,30 +0,0 @@
1
- __init__.py,sha256=m27nXDOFpStOzfg3Qqeaw7yj1wNmpnOH7xk5svlCprA,1185
2
- analyzer/__init__.py,sha256=m27nXDOFpStOzfg3Qqeaw7yj1wNmpnOH7xk5svlCprA,1185
3
- analyzer/cli.py,sha256=x6ZfhtTyJbLDSA8eNlKpXdeHc0nvvp0jdN40mYFHXPs,10488
4
- analyzer/engine.py,sha256=rbJtEQj1_Qr69Z3Uzx3c97iCw1BkS8A4gQv9WnV0Egk,22241
5
- analyzer/fixer.py,sha256=6CujODr4pJgdR2XuJoBOCrQxBHjJfvVsBmg4YZdKJAM,10653
6
- analyzer/scanner.py,sha256=o4pvtUnYn2ZYo5OhEwswm7BEeIA61XrosmrhS5Al8SI,34260
7
- analyzer/semantic.py,sha256=cVmSiTmI-b4DFZ5Q6kZUdqd6ZPH92pJmNAE60MIiZRI,8192
8
- analyzer/transformers.py,sha256=tFmelReM8ILpFjMBSDIDMCXPSZlD9t8FD7lTXVgfOgA,7103
9
- analyzer/validators.py,sha256=5JPwQYQ4KKThZchr34nV06-VToFhgBOtIp7lNhO5WTk,8344
10
- analyzer/models/__init__.py,sha256=NlDgdV1i9CoWJBgiENoJQuHnYCOzO6U_ui3wHOeT7xI,160
11
- analyzer/models/analysis_models.py,sha256=WXsYgbjD4ngBoyaY-YkwMMl2Jpbn96hue6zuSERZRb0,2362
12
- analyzer/reporters/__init__.py,sha256=fVsu2gCuBdDd_STD97Jku8otcO3j7NZrxxd_pGzf6Sw,280
13
- analyzer/reporters/html_reporter.py,sha256=j64KkjiRwwRBzIPZE7bBJGsbJEYIoSnZeho63tI4c5A,14919
14
- analyzer/reporters/markdown_reporter.py,sha256=sc0zBM4_hJmLE8tZF8lV_v7VLRlNSEhWqSivFZMQKac,8074
15
- analyzer/reporters/summary_reporter.py,sha256=v4XfmcE027fCIWmS5N0odCx-NdziWuCefBIacRhi8XA,7237
16
- analyzer/rules/__init__.py,sha256=UNZoY89SFQoUKYGj_rjwvv8md6vXSSM2pGPV6MqC2Fg,261
17
- analyzer/rules/modernization_rules.py,sha256=IJw8c6v7Q_5z3xF9Dh0eBrproRFZFtIOyWcArAsSv18,1071
18
- analyzer/rules/qgis_rules.py,sha256=N_1RxWrckOZWzcAMPI-07vLQPazO6MM7-LF69_NFMy8,2548
19
- analyzer/utils/__init__.py,sha256=7abAIymmrA8FlzqsM8JfKXnfx3HjX0r-8KdvmgKMRQA,1006
20
- analyzer/utils/ast_utils.py,sha256=LZ9NLrbJlhFGM7LsX6YNexOi_avJ0Sxzf8GdZHvoXK4,3831
21
- analyzer/utils/config_utils.py,sha256=reNyUonmik8Tpwx0RbaslsiienIqPDeiXBfh4Pv23_E,4690
22
- analyzer/utils/logging_utils.py,sha256=V0z7XeF46tj2oI247j2fRRafClbtMDVscVmg2JFYHV4,1257
23
- analyzer/utils/path_utils.py,sha256=BkffM1TRKAMPCzLx49rQzmYHYGbPwlI41LCK5QoN2oU,4097
24
- analyzer/utils/performance_utils.py,sha256=M3mfMVrOvyfgQiCQB0R5IkkBnAx-L9er4fGR9UL5E-0,4253
25
- qgis_plugin_analyzer-1.4.0.dist-info/licenses/LICENSE,sha256=bMxViCXCE9h3bzmw6oUvy4R_CvzOrV3aYhIvjBPx20A,35091
26
- qgis_plugin_analyzer-1.4.0.dist-info/METADATA,sha256=nwhnhEsDydRGcYiq-OkdbqxYn3JptRCRcH90_WvloLg,12906
27
- qgis_plugin_analyzer-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- qgis_plugin_analyzer-1.4.0.dist-info/entry_points.txt,sha256=1dwiqkZC4hGYExrOgyhZkxv7CbClSZqJkVk42nlw1IY,52
29
- qgis_plugin_analyzer-1.4.0.dist-info/top_level.txt,sha256=i9U1DboCuTuaYpS-5GcgUeKIlSI00PoxYtnrfQWD8wo,18
30
- qgis_plugin_analyzer-1.4.0.dist-info/RECORD,,