qgis-plugin-analyzer 1.5.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/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 -184
- analyzer/commands.py +7 -7
- analyzer/engine.py +421 -238
- analyzer/fixer.py +206 -130
- analyzer/reporters/markdown_reporter.py +48 -15
- analyzer/reporters/summary_reporter.py +193 -80
- analyzer/scanner.py +218 -138
- 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.5.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/METADATA +16 -7
- qgis_plugin_analyzer-1.6.0.dist-info/RECORD +52 -0
- analyzer/visitors.py +0 -455
- qgis_plugin_analyzer-1.5.0.dist-info/RECORD +0 -35
- {qgis_plugin_analyzer-1.5.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/WHEEL +0 -0
- {qgis_plugin_analyzer-1.5.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/entry_points.txt +0 -0
- {qgis_plugin_analyzer-1.5.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {qgis_plugin_analyzer-1.5.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
|
|
@@ -62,20 +62,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
|
|
|
62
62
|
|
|
63
63
|
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
64
64
|
| :--- | :---: | :---: | :---: | :---: |
|
|
65
|
+
| **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
|
|
65
66
|
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
66
67
|
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
67
68
|
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
68
69
|
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
69
|
-
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ |
|
|
70
|
-
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ |
|
|
70
|
+
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
|
|
71
|
+
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
|
|
71
72
|
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
72
73
|
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
73
74
|
|
|
74
75
|
### Key Differentiators
|
|
75
76
|
|
|
76
|
-
1. **
|
|
77
|
-
2. **
|
|
78
|
-
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.
|
|
79
80
|
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
80
81
|
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
81
82
|
|
|
@@ -131,7 +132,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
|
|
|
131
132
|
|
|
132
133
|
```yaml
|
|
133
134
|
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
134
|
-
rev:
|
|
135
|
+
rev: main # Use 'main' for latest features or a specific tag like v1.5.0
|
|
135
136
|
hooks:
|
|
136
137
|
- id: qgis-plugin-analyzer
|
|
137
138
|
```
|
|
@@ -190,6 +191,7 @@ Audits an existing QGIS plugin repository.
|
|
|
190
191
|
| :--- | :--- | :--- |
|
|
191
192
|
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
192
193
|
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
194
|
+
| `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
|
|
193
195
|
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
194
196
|
|
|
195
197
|
### `qgis-analyzer fix`
|
|
@@ -218,6 +220,7 @@ Performs a focused security scan on a file or directory.
|
|
|
218
220
|
| Argument | Description | Default |
|
|
219
221
|
| :--- | :--- | :--- |
|
|
220
222
|
| `path` | **(Required)** Path to the file or directory to scan. | N/A |
|
|
223
|
+
| `--deep` | Run more intensive (but slower) security checks. | `False` |
|
|
221
224
|
| `-p`, `--profile`| Configuration profile. | `default` |
|
|
222
225
|
|
|
223
226
|
### `qgis-analyzer version`
|
|
@@ -258,6 +261,7 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
258
261
|
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
259
262
|
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
260
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.
|
|
261
265
|
|
|
262
266
|
### Industry & Community Standards
|
|
263
267
|
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
@@ -267,6 +271,11 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
267
271
|
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
268
272
|
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
269
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
|
+
|
|
270
279
|
### Internal Resources
|
|
271
280
|
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
272
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,,
|