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.
- analyzer/__init__.py +2 -1
- analyzer/cli/__init__.py +14 -0
- analyzer/cli/app.py +147 -0
- analyzer/cli/base.py +93 -0
- analyzer/cli/commands/__init__.py +19 -0
- analyzer/cli/commands/analyze.py +47 -0
- analyzer/cli/commands/fix.py +58 -0
- analyzer/cli/commands/init.py +41 -0
- analyzer/cli/commands/list_rules.py +41 -0
- analyzer/cli/commands/security.py +46 -0
- analyzer/cli/commands/summary.py +52 -0
- analyzer/cli/commands/version.py +41 -0
- analyzer/cli.py +4 -281
- analyzer/commands.py +163 -0
- analyzer/engine.py +491 -245
- analyzer/fixer.py +206 -130
- analyzer/reporters/markdown_reporter.py +88 -14
- analyzer/reporters/summary_reporter.py +226 -49
- analyzer/rules/qgis_rules.py +3 -1
- analyzer/scanner.py +219 -711
- analyzer/secrets.py +84 -0
- analyzer/security_checker.py +85 -0
- analyzer/security_rules.py +127 -0
- analyzer/transformers.py +29 -8
- analyzer/utils/__init__.py +2 -0
- analyzer/utils/path_utils.py +53 -1
- analyzer/validators.py +90 -55
- analyzer/visitors/__init__.py +19 -0
- analyzer/visitors/base.py +75 -0
- analyzer/visitors/composite_visitor.py +73 -0
- analyzer/visitors/imports_visitor.py +85 -0
- analyzer/visitors/metrics_visitor.py +158 -0
- analyzer/visitors/security_visitor.py +52 -0
- analyzer/visitors/standards_visitor.py +284 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/METADATA +32 -10
- qgis_plugin_analyzer-1.6.0.dist-info/RECORD +52 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/WHEEL +1 -1
- qgis_plugin_analyzer-1.4.0.dist-info/RECORD +0 -30
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/entry_points.txt +0 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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
|

|
|
35
35
|

|
|
36
36
|

|
|
37
|
-

|
|
38
|
+

|
|
39
|
+

|
|
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
|
-
| **
|
|
67
|
-
| **
|
|
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. **
|
|
75
|
-
2. **
|
|
76
|
-
3. **
|
|
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:
|
|
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,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,,
|
{qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|