sourcecode 0.17.0__py3-none-any.whl → 0.19.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.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +8 -0
- sourcecode/dependency_analyzer.py +4 -0
- sourcecode/detectors/java.py +104 -7
- sourcecode/env_analyzer.py +31 -0
- sourcecode/schema.py +1 -0
- sourcecode/serializer.py +1 -0
- {sourcecode-0.17.0.dist-info → sourcecode-0.19.0.dist-info}/METADATA +1 -1
- {sourcecode-0.17.0.dist-info → sourcecode-0.19.0.dist-info}/RECORD +11 -11
- {sourcecode-0.17.0.dist-info → sourcecode-0.19.0.dist-info}/WHEEL +0 -0
- {sourcecode-0.17.0.dist-info → sourcecode-0.19.0.dist-info}/entry_points.txt +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -260,6 +260,14 @@ def main(
|
|
|
260
260
|
file_tree = filter_sensitive_files(raw_tree)
|
|
261
261
|
detector = ProjectDetector(build_default_detectors())
|
|
262
262
|
workspace_analysis = WorkspaceAnalyzer().analyze(target, manifests)
|
|
263
|
+
|
|
264
|
+
# --compact implicitly enables lightweight analysis passes so that
|
|
265
|
+
# dependency_summary, env_summary and code_notes_summary are never null.
|
|
266
|
+
if compact:
|
|
267
|
+
dependencies = True
|
|
268
|
+
env_map = True
|
|
269
|
+
code_notes = True
|
|
270
|
+
|
|
263
271
|
dependency_analyzer = DependencyAnalyzer() if dependencies else None
|
|
264
272
|
graph_analyzer = GraphAnalyzer() if graph_modules else None
|
|
265
273
|
parsed_graph_edges = (
|
|
@@ -47,6 +47,7 @@ class DependencyAnalyzer:
|
|
|
47
47
|
ecosystems: set[str] = set()
|
|
48
48
|
sources: set[str] = set()
|
|
49
49
|
limitations: list[str] = []
|
|
50
|
+
all_dependencies: list[DependencyRecord] = []
|
|
50
51
|
for summary in summaries:
|
|
51
52
|
result.total_count += summary.total_count
|
|
52
53
|
result.direct_count += summary.direct_count
|
|
@@ -56,9 +57,11 @@ class DependencyAnalyzer:
|
|
|
56
57
|
for limitation in summary.limitations:
|
|
57
58
|
if limitation not in limitations:
|
|
58
59
|
limitations.append(limitation)
|
|
60
|
+
all_dependencies.extend(summary.dependencies)
|
|
59
61
|
result.ecosystems = sorted(ecosystems)
|
|
60
62
|
result.sources = sorted(sources)
|
|
61
63
|
result.limitations = limitations
|
|
64
|
+
result.dependencies = all_dependencies
|
|
62
65
|
return result
|
|
63
66
|
|
|
64
67
|
def _build_summary(
|
|
@@ -80,6 +83,7 @@ class DependencyAnalyzer:
|
|
|
80
83
|
ecosystems=ecosystems,
|
|
81
84
|
sources=sources,
|
|
82
85
|
limitations=unique_limitations,
|
|
86
|
+
dependencies=list(records),
|
|
83
87
|
)
|
|
84
88
|
|
|
85
89
|
def _dedupe(self, records: Iterable[DependencyRecord]) -> list[DependencyRecord]:
|
sourcecode/detectors/java.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from xml.etree import ElementTree
|
|
5
6
|
|
|
@@ -13,6 +14,14 @@ from sourcecode.detectors.parsers import read_text_lines, unique_strings
|
|
|
13
14
|
from sourcecode.schema import FrameworkDetection
|
|
14
15
|
from sourcecode.tree_utils import flatten_file_tree
|
|
15
16
|
|
|
17
|
+
_MAX_FILE_SIZE = 256 * 1024 # 256 KB
|
|
18
|
+
_MAX_JAVA_ENTRY_SCAN = 200
|
|
19
|
+
_MAX_ANNOTATION_ENTRY_POINTS = 20
|
|
20
|
+
|
|
21
|
+
_REST_CONTROLLER_RE = re.compile(r'@(?:Rest)?Controller\b')
|
|
22
|
+
_WEB_FILTER_RE = re.compile(r'@WebFilter\b')
|
|
23
|
+
_FILTER_BEAN_RE = re.compile(r'FilterRegistrationBean\b')
|
|
24
|
+
|
|
16
25
|
|
|
17
26
|
class JavaDetector(AbstractDetector):
|
|
18
27
|
name = "java"
|
|
@@ -67,15 +76,103 @@ class JavaDetector(AbstractDetector):
|
|
|
67
76
|
return frameworks
|
|
68
77
|
|
|
69
78
|
def _collect_entry_points(self, context: DetectionContext) -> list[EntryPoint]:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
all_paths = flatten_file_tree(context.file_tree)
|
|
80
|
+
all_java = [p for p in all_paths if p.endswith(".java")]
|
|
81
|
+
|
|
82
|
+
# 1. @SpringBootApplication entry: Application.java / Main.java by name
|
|
83
|
+
app_candidates = [
|
|
84
|
+
p for p in all_java
|
|
85
|
+
if p.endswith(("Application.java", "Main.java"))
|
|
74
86
|
]
|
|
75
|
-
|
|
76
|
-
EntryPoint(path=
|
|
77
|
-
for
|
|
87
|
+
entry_points: list[EntryPoint] = [
|
|
88
|
+
EntryPoint(path=p, stack="java", kind="application", source="manifest")
|
|
89
|
+
for p in unique_strings(app_candidates)
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
# 2. Annotation-based scan: @RestController, @WebFilter, FilterRegistrationBean
|
|
93
|
+
scan_candidates = [
|
|
94
|
+
p for p in all_java
|
|
95
|
+
if "/test/" not in p and "/tests/" not in p
|
|
96
|
+
][:_MAX_JAVA_ENTRY_SCAN]
|
|
97
|
+
|
|
98
|
+
annotation_eps: list[EntryPoint] = []
|
|
99
|
+
for rel_path in scan_candidates:
|
|
100
|
+
if len(annotation_eps) >= _MAX_ANNOTATION_ENTRY_POINTS:
|
|
101
|
+
break
|
|
102
|
+
abs_path = context.root / rel_path
|
|
103
|
+
annotation_eps.extend(self._scan_java_file_for_entry_points(abs_path, rel_path))
|
|
104
|
+
entry_points.extend(annotation_eps)
|
|
105
|
+
|
|
106
|
+
# 3. web.xml servlet/filter declarations
|
|
107
|
+
web_xml_paths = [
|
|
108
|
+
p for p in all_paths
|
|
109
|
+
if p.endswith("web.xml") and "WEB-INF" in p
|
|
78
110
|
]
|
|
111
|
+
for rel_path in web_xml_paths[:1]:
|
|
112
|
+
abs_path = context.root / rel_path
|
|
113
|
+
entry_points.extend(self._parse_web_xml(abs_path, rel_path))
|
|
114
|
+
|
|
115
|
+
# Deduplicate by (path, kind)
|
|
116
|
+
seen: set[tuple[str, str]] = set()
|
|
117
|
+
unique_eps: list[EntryPoint] = []
|
|
118
|
+
for ep in entry_points:
|
|
119
|
+
key = (ep.path, ep.kind)
|
|
120
|
+
if key not in seen:
|
|
121
|
+
seen.add(key)
|
|
122
|
+
unique_eps.append(ep)
|
|
123
|
+
return unique_eps
|
|
124
|
+
|
|
125
|
+
def _scan_java_file_for_entry_points(self, abs_path: Path, rel_path: str) -> list[EntryPoint]:
|
|
126
|
+
try:
|
|
127
|
+
if abs_path.stat().st_size > _MAX_FILE_SIZE:
|
|
128
|
+
return []
|
|
129
|
+
content = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
130
|
+
except OSError:
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
# Quick pre-filter before running regexes
|
|
134
|
+
if "Controller" not in content and "Filter" not in content:
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
if _REST_CONTROLLER_RE.search(content):
|
|
138
|
+
return [EntryPoint(
|
|
139
|
+
path=rel_path, stack="java", kind="http_handler",
|
|
140
|
+
source="annotation", confidence="high",
|
|
141
|
+
)]
|
|
142
|
+
if _WEB_FILTER_RE.search(content):
|
|
143
|
+
return [EntryPoint(
|
|
144
|
+
path=rel_path, stack="java", kind="filter",
|
|
145
|
+
source="annotation", confidence="high",
|
|
146
|
+
)]
|
|
147
|
+
if _FILTER_BEAN_RE.search(content):
|
|
148
|
+
return [EntryPoint(
|
|
149
|
+
path=rel_path, stack="java", kind="filter",
|
|
150
|
+
source="annotation", confidence="medium",
|
|
151
|
+
)]
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def _parse_web_xml(self, abs_path: Path, rel_path: str) -> list[EntryPoint]:
|
|
155
|
+
try:
|
|
156
|
+
tree = ElementTree.parse(abs_path)
|
|
157
|
+
except (OSError, ElementTree.ParseError):
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
root = tree.getroot()
|
|
161
|
+
ns_match = re.match(r"\{[^}]+\}", root.tag)
|
|
162
|
+
ns = ns_match.group(0) if ns_match else ""
|
|
163
|
+
|
|
164
|
+
results: list[EntryPoint] = []
|
|
165
|
+
if root.findall(f".//{ns}servlet-class"):
|
|
166
|
+
results.append(EntryPoint(
|
|
167
|
+
path=rel_path, stack="java", kind="servlet",
|
|
168
|
+
source="xml_config", confidence="high",
|
|
169
|
+
))
|
|
170
|
+
if root.findall(f".//{ns}filter-class"):
|
|
171
|
+
results.append(EntryPoint(
|
|
172
|
+
path=rel_path, stack="java", kind="filter",
|
|
173
|
+
source="xml_config", confidence="high",
|
|
174
|
+
))
|
|
175
|
+
return results
|
|
79
176
|
|
|
80
177
|
def _dedupe_frameworks(self, frameworks: list[FrameworkDetection]) -> list[FrameworkDetection]:
|
|
81
178
|
seen: set[str] = set()
|
sourcecode/env_analyzer.py
CHANGED
|
@@ -25,6 +25,12 @@ _ENV_EXAMPLE_NAMES = {
|
|
|
25
25
|
"example.env", "sample.env",
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
# Spring Boot application.properties / application.yml and their profile variants
|
|
29
|
+
_SPRING_CONF_BASE = {"application.properties", "application.yml", "application.yaml"}
|
|
30
|
+
_SPRING_CONF_PROFILE_RE = re.compile(r'^application-[a-z0-9_-]+\.(properties|ya?ml)$', re.IGNORECASE)
|
|
31
|
+
# Matches ${ENV_VAR} or ${ENV_VAR:default} where ENV_VAR is UPPER_SNAKE_CASE
|
|
32
|
+
_SPRING_ENV_REF_RE = re.compile(r'\$\{([A-Z][A-Z0-9_]*)(?::[^}]*)?\}')
|
|
33
|
+
|
|
28
34
|
# (pattern_id, compiled_regex)
|
|
29
35
|
# Grupos de captura: group(1)=key, group(2)=default si existe
|
|
30
36
|
_PATTERNS: list[tuple[str, re.Pattern]] = [
|
|
@@ -55,6 +61,9 @@ _PATTERNS: list[tuple[str, re.Pattern]] = [
|
|
|
55
61
|
("java_getenv", re.compile(
|
|
56
62
|
r"""System\.getenv\(\s*["']([A-Z][A-Z0-9_]*)["']\s*\)"""
|
|
57
63
|
)),
|
|
64
|
+
("java_spring_value", re.compile(
|
|
65
|
+
r"""@Value\(\s*["']\$\{([A-Z][A-Z0-9_]*)(?::[^}]*)?\}["']\s*\)"""
|
|
66
|
+
)),
|
|
58
67
|
("php_getenv", re.compile(
|
|
59
68
|
r"""getenv\(\s*["']([A-Z][A-Z0-9_]*)["']\s*\)"""
|
|
60
69
|
)),
|
|
@@ -188,6 +197,23 @@ def _parse_env_example(
|
|
|
188
197
|
return results
|
|
189
198
|
|
|
190
199
|
|
|
200
|
+
def _parse_spring_config(
|
|
201
|
+
path: Path,
|
|
202
|
+
rel_path: str,
|
|
203
|
+
findings: dict,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Parse application.properties / application.yml looking for ${ENV_VAR} refs."""
|
|
206
|
+
try:
|
|
207
|
+
content = path.read_text(encoding="utf-8", errors="replace")
|
|
208
|
+
except OSError:
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
for m in _SPRING_ENV_REF_RE.finditer(content):
|
|
212
|
+
key = m.group(1)
|
|
213
|
+
line_num = content.count("\n", 0, m.start()) + 1
|
|
214
|
+
findings[key].append((f"{rel_path}:{line_num}", None))
|
|
215
|
+
|
|
216
|
+
|
|
191
217
|
class EnvAnalyzer:
|
|
192
218
|
"""Extrae el mapa de variables de entorno del proyecto."""
|
|
193
219
|
|
|
@@ -298,11 +324,16 @@ class EnvAnalyzer:
|
|
|
298
324
|
self._walk(root, entry, findings, example_entries, example_files_found, limitations)
|
|
299
325
|
elif entry.is_file():
|
|
300
326
|
rel = entry.relative_to(root).as_posix()
|
|
327
|
+
name_lower = name.lower()
|
|
301
328
|
# .env.example and similar
|
|
302
329
|
if name in _ENV_EXAMPLE_NAMES:
|
|
303
330
|
example_files_found.append(rel)
|
|
304
331
|
example_entries.extend(_parse_env_example(entry, rel))
|
|
305
332
|
continue
|
|
333
|
+
# Spring Boot application.properties / application.yml (incl. profiles)
|
|
334
|
+
if name_lower in _SPRING_CONF_BASE or _SPRING_CONF_PROFILE_RE.match(name_lower):
|
|
335
|
+
_parse_spring_config(entry, rel, findings)
|
|
336
|
+
continue
|
|
306
337
|
# Source code files
|
|
307
338
|
suffix = entry.suffix.lower()
|
|
308
339
|
if suffix in _CODE_EXTENSIONS:
|
sourcecode/schema.py
CHANGED
|
@@ -96,6 +96,7 @@ class DependencySummary:
|
|
|
96
96
|
ecosystems: list[str] = field(default_factory=list)
|
|
97
97
|
sources: list[str] = field(default_factory=list)
|
|
98
98
|
limitations: list[str] = field(default_factory=list)
|
|
99
|
+
dependencies: list["DependencyRecord"] = field(default_factory=list)
|
|
99
100
|
|
|
100
101
|
|
|
101
102
|
@dataclass
|
sourcecode/serializer.py
CHANGED
|
@@ -74,6 +74,7 @@ def compact_view(sm: SourceMap) -> dict[str, Any]:
|
|
|
74
74
|
dep_summary_dict: Any = None
|
|
75
75
|
if sm.dependency_summary is not None and sm.dependency_summary.requested:
|
|
76
76
|
dep_summary_dict = asdict(sm.dependency_summary)
|
|
77
|
+
dep_summary_dict.pop("dependencies", None)
|
|
77
78
|
|
|
78
79
|
env_summary_dict: Any = None
|
|
79
80
|
if sm.env_summary is not None and sm.env_summary.requested:
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=K3oF2tC36Se4IiYU6MvNhrg4EoK_jaLeik_nUWPID4Y,100
|
|
2
2
|
sourcecode/architecture_analyzer.py,sha256=zxgqeqn2FhIUDn06X-whjGKiXbrHFkRlzZcr2zKd0Gw,16874
|
|
3
3
|
sourcecode/architecture_summary.py,sha256=r9IciS9z-7h8xpiME7DKBLhLV36xzXtSzbZ1cRDWklo,11709
|
|
4
4
|
sourcecode/classifier.py,sha256=fcp2wIq198wq3Rsh_bDmZIK_W5UI6FEL5oqm1RrQfpA,6846
|
|
5
|
-
sourcecode/cli.py,sha256=
|
|
5
|
+
sourcecode/cli.py,sha256=pB0NmyaH-gCrHCFlNxYSMfuArM6nitv6PyrMLkYKKRs,25429
|
|
6
6
|
sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
|
|
7
7
|
sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
|
|
8
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
8
|
+
sourcecode/dependency_analyzer.py,sha256=KITti0XLHK3RvZGqAUseWnlFLXGFyv5FqvSK5R6PB1g,41147
|
|
9
9
|
sourcecode/doc_analyzer.py,sha256=Ec3orx6vBKsh5cNM3-F4y2Got2KuKx8w3dErwtdtM-A,19891
|
|
10
|
-
sourcecode/env_analyzer.py,sha256=
|
|
10
|
+
sourcecode/env_analyzer.py,sha256=JZxBOuIxnM0xw0IaJFL6LiPJBErL848L3XoTOHGBqZI,13554
|
|
11
11
|
sourcecode/git_analyzer.py,sha256=S9PGt8RDasBQYQUsZh9-H_KWVlPvzwR4jgM7TKN9ZCk,7643
|
|
12
12
|
sourcecode/graph_analyzer.py,sha256=cxl7Xb88aGxIVOCsatZJAE1CtgXGzIbJfqytV-9kkFs,45172
|
|
13
13
|
sourcecode/metrics_analyzer.py,sha256=4uh11v-Q0gdrN87BOxuFWUym3N3AOkOuy21K5N8peB8,20126
|
|
14
14
|
sourcecode/prepare_context.py,sha256=X43dEPe2cb7uMwlpQwVJMe0rQIZ0juaJ7H3F37sPbNc,5371
|
|
15
15
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
16
16
|
sourcecode/scanner.py,sha256=Ga4jDRAOhkcwlRmlE2ko6cE39qKy0aI4iE1lGBWmAlk,6410
|
|
17
|
-
sourcecode/schema.py,sha256=
|
|
17
|
+
sourcecode/schema.py,sha256=dnNdnQxJtBv0PRVWi6-xnpr16dj6IsUGbso9PeeEolA,15743
|
|
18
18
|
sourcecode/semantic_analyzer.py,sha256=SRQSGJzEi4ZsGvb7U3qvnRiKHabz4hvIzCqiG92x9K8,79349
|
|
19
|
-
sourcecode/serializer.py,sha256=
|
|
19
|
+
sourcecode/serializer.py,sha256=edOfD25lUKkZ0gIjFcjqWmY4yHSnOKvudzNisvW_RYg,13456
|
|
20
20
|
sourcecode/summarizer.py,sha256=2Yz5xJ1bhtJLlrUpKcd74wijypRRo9GJmGT8u971EW0,11572
|
|
21
21
|
sourcecode/tree_utils.py,sha256=xla45Whq24j6U9kEgTztJXkMpmOEDieAulKxP3nl89s,935
|
|
22
22
|
sourcecode/workspace.py,sha256=fQlVoNx8S-fSHpKoJ0JBvEHCFkxszH0KZVJed1i3TRk,6845
|
|
@@ -27,7 +27,7 @@ sourcecode/detectors/dotnet.py,sha256=UC-74VW850r3admcl36R-rl3M0-nBFXw8ph5e9RSas
|
|
|
27
27
|
sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJE,1713
|
|
28
28
|
sourcecode/detectors/go.py,sha256=KehAyvC3H_s8zwwqFM0bAbztQbbUBDjpNbGvM2kQlk8,1836
|
|
29
29
|
sourcecode/detectors/heuristic.py,sha256=GvniuNzeCOzmKyY5Sd88_bmBnVrJE5O9cbs4NoEPBKY,1891
|
|
30
|
-
sourcecode/detectors/java.py,sha256=
|
|
30
|
+
sourcecode/detectors/java.py,sha256=qFVpMy5mu490ENhutO1LV2Gidms4ZZUfNX5Qv5Qxh2I,7188
|
|
31
31
|
sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
|
|
32
32
|
sourcecode/detectors/nodejs.py,sha256=cCK50aY7RERQUQ7FK2UmqVTVFAW3VksDxN6rYADtsC0,4498
|
|
33
33
|
sourcecode/detectors/parsers.py,sha256=ugPg8yNUf0Ai1gA7Fnn6wAkYGFjTxRodSP3IeViYJJ4,2290
|
|
@@ -39,7 +39,7 @@ sourcecode/detectors/rust.py,sha256=V23NO6Y8NsrhrUlGC2feUEcNK63hf3gc1y3jI4tes1Q,
|
|
|
39
39
|
sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5TcqQ,1627
|
|
40
40
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
41
41
|
sourcecode/detectors/tooling.py,sha256=hIvop80No22pqyGVJ32NKliSdjkHRePQkIRroqG01bY,1875
|
|
42
|
-
sourcecode-0.
|
|
43
|
-
sourcecode-0.
|
|
44
|
-
sourcecode-0.
|
|
45
|
-
sourcecode-0.
|
|
42
|
+
sourcecode-0.19.0.dist-info/METADATA,sha256=cv1q6sPPTkTruEdpHr8JioUXmXuSec5dPAhAw9NXoW8,25900
|
|
43
|
+
sourcecode-0.19.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
44
|
+
sourcecode-0.19.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
|
|
45
|
+
sourcecode-0.19.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|