sourcecode 1.72.0__py3-none-any.whl → 1.73.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 +34 -6
- sourcecode/detectors/java.py +99 -8
- sourcecode/detectors/parsers.py +23 -0
- sourcecode/integration_detector.py +43 -0
- sourcecode/migrate_check.py +4 -12
- sourcecode/repository_ir.py +35 -0
- sourcecode/serializer.py +6 -1
- {sourcecode-1.72.0.dist-info → sourcecode-1.73.0.dist-info}/METADATA +1 -1
- {sourcecode-1.72.0.dist-info → sourcecode-1.73.0.dist-info}/RECORD +13 -13
- {sourcecode-1.72.0.dist-info → sourcecode-1.73.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.72.0.dist-info → sourcecode-1.73.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.72.0.dist-info → sourcecode-1.73.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -4221,6 +4221,7 @@ def _build_c4_export(
|
|
|
4221
4221
|
edges: "list[dict]",
|
|
4222
4222
|
endpoints: "list[dict]",
|
|
4223
4223
|
integrations: "dict",
|
|
4224
|
+
endpoint_meta: "Optional[dict]" = None,
|
|
4224
4225
|
) -> "dict":
|
|
4225
4226
|
"""Assemble a unified, tool-agnostic C4 architecture export + incremental manifest.
|
|
4226
4227
|
|
|
@@ -4236,6 +4237,15 @@ def _build_c4_export(
|
|
|
4236
4237
|
# consumer counts/classifies modules instead of inferring them from leaf dirs.
|
|
4237
4238
|
module_graph["module_roots"] = _detect_module_roots(by_directory)
|
|
4238
4239
|
api_surface = _group_endpoints_by_controller(endpoints)
|
|
4240
|
+
# BUG #7 (Alfresco field test): carry the endpoint extractor's structured
|
|
4241
|
+
# zero-result explanation and non-Spring REST surface into api_surface, so a
|
|
4242
|
+
# C4 consumer seeing 0 controllers understands WHY (unrecognized routing, e.g.
|
|
4243
|
+
# WebScripts) instead of concluding the system has no public API.
|
|
4244
|
+
if endpoint_meta:
|
|
4245
|
+
if endpoint_meta.get("zero_result_reason"):
|
|
4246
|
+
api_surface["zero_result_reason"] = endpoint_meta["zero_result_reason"]
|
|
4247
|
+
if endpoint_meta.get("non_spring_rest_surface"):
|
|
4248
|
+
api_surface["non_spring_rest_surface"] = endpoint_meta["non_spring_rest_surface"]
|
|
4239
4249
|
containers = _detect_containers(root)
|
|
4240
4250
|
|
|
4241
4251
|
limitations: "list[str]" = []
|
|
@@ -4364,7 +4374,8 @@ def export_cmd(
|
|
|
4364
4374
|
from sourcecode.repository_ir import extract_java_endpoints
|
|
4365
4375
|
ir = build_repo_ir(file_list, root)
|
|
4366
4376
|
graph = ir.get("graph", {})
|
|
4367
|
-
|
|
4377
|
+
_ep_data = extract_java_endpoints(root)
|
|
4378
|
+
endpoints = _ep_data.get("endpoints", [])
|
|
4368
4379
|
data = _build_c4_export(
|
|
4369
4380
|
root,
|
|
4370
4381
|
file_list,
|
|
@@ -4372,6 +4383,7 @@ def export_cmd(
|
|
|
4372
4383
|
graph.get("edges", []),
|
|
4373
4384
|
endpoints,
|
|
4374
4385
|
detect_integrations(file_list, root),
|
|
4386
|
+
endpoint_meta=_ep_data,
|
|
4375
4387
|
)
|
|
4376
4388
|
output = _serialize_dict(data, format)
|
|
4377
4389
|
_emit_command_output(output, output_path, copy,
|
|
@@ -5215,14 +5227,16 @@ def explain_cmd(
|
|
|
5215
5227
|
Path("."),
|
|
5216
5228
|
help="Repository root (default: current directory)",
|
|
5217
5229
|
),
|
|
5218
|
-
format: str = typer.Option(
|
|
5219
|
-
|
|
5220
|
-
help="Output format: text (default) or json."
|
|
5221
|
-
|
|
5230
|
+
format: Optional[str] = typer.Option(
|
|
5231
|
+
None, "--format", "-f",
|
|
5232
|
+
help="Output format: text (default) or json. If unset and --output ends "
|
|
5233
|
+
"in .json, JSON is written automatically.",
|
|
5222
5234
|
),
|
|
5223
5235
|
output_path: Optional[Path] = typer.Option(
|
|
5224
5236
|
None, "--output", "-o",
|
|
5225
|
-
help="Write output to a file instead of stdout."
|
|
5237
|
+
help="Write output to a file instead of stdout. NOTE: the default format is "
|
|
5238
|
+
"human-readable text/Markdown, NOT JSON — pass --format json (or use a "
|
|
5239
|
+
".json output path) for machine-readable output.",
|
|
5226
5240
|
),
|
|
5227
5241
|
copy: bool = typer.Option(
|
|
5228
5242
|
False, "--copy", "-c",
|
|
@@ -5231,6 +5245,11 @@ def explain_cmd(
|
|
|
5231
5245
|
) -> None:
|
|
5232
5246
|
"""Human-readable architectural summary for a class.
|
|
5233
5247
|
|
|
5248
|
+
\b
|
|
5249
|
+
OUTPUT FORMAT: defaults to text/Markdown (human-oriented). Unlike most
|
|
5250
|
+
subcommands this is NOT JSON by default — pass --format json, or give an
|
|
5251
|
+
--output path ending in .json, for a machine-readable envelope.
|
|
5252
|
+
|
|
5234
5253
|
\b
|
|
5235
5254
|
Generates a structured explanation derived entirely from static analysis:
|
|
5236
5255
|
- Purpose and Spring stereotype
|
|
@@ -5278,6 +5297,15 @@ def explain_cmd(
|
|
|
5278
5297
|
)
|
|
5279
5298
|
raise typer.Exit(code=1)
|
|
5280
5299
|
|
|
5300
|
+
# BUG #2 (Alfresco field test): `explain -o out.json` previously wrote Markdown
|
|
5301
|
+
# to a .json file, breaking any programmatic consumer that assumed JSON parity
|
|
5302
|
+
# with the other subcommands. Infer JSON from a .json output extension when
|
|
5303
|
+
# --format is unset; an explicit --format always wins.
|
|
5304
|
+
if format is None:
|
|
5305
|
+
if output_path and str(output_path).lower().endswith(".json"):
|
|
5306
|
+
format = "json"
|
|
5307
|
+
else:
|
|
5308
|
+
format = "text"
|
|
5281
5309
|
_enforce_format("explain", format)
|
|
5282
5310
|
|
|
5283
5311
|
file_list = find_java_files(target)
|
sourcecode/detectors/java.py
CHANGED
|
@@ -10,7 +10,11 @@ from sourcecode.detectors.base import (
|
|
|
10
10
|
EntryPoint,
|
|
11
11
|
StackDetection,
|
|
12
12
|
)
|
|
13
|
-
from sourcecode.detectors.parsers import
|
|
13
|
+
from sourcecode.detectors.parsers import (
|
|
14
|
+
read_text_lines,
|
|
15
|
+
substitute_maven_properties as _resolve_maven_property,
|
|
16
|
+
unique_strings,
|
|
17
|
+
)
|
|
14
18
|
from sourcecode.schema import FrameworkDetection
|
|
15
19
|
from sourcecode.tree_utils import flatten_file_tree
|
|
16
20
|
|
|
@@ -55,6 +59,14 @@ _JAX_RS_PROVIDER_RE = re.compile(r'@Provider\b')
|
|
|
55
59
|
|
|
56
60
|
# --- CDI / Jakarta EE scopes ---
|
|
57
61
|
_CDI_SCOPED_RE = re.compile(r'@(?:ApplicationScoped|RequestScoped|SessionScoped|Singleton|Dependent)\b')
|
|
62
|
+
# BUG #3: a genuine bootstrap entry declares a real main() or a bootstrap annotation
|
|
63
|
+
# / WAR initializer supertype — never merely a `*Application.java` filename.
|
|
64
|
+
_MAIN_METHOD_RE = re.compile(r'\bstatic\b[^;{}()]*\bmain\s*\(\s*(?:final\s+)?String')
|
|
65
|
+
_BOOTSTRAP_ANNOTATION_RE = re.compile(
|
|
66
|
+
r'@(?:SpringBootApplication|QuarkusMain|MicronautApplication|'
|
|
67
|
+
r'EnableAutoConfiguration|SpringBootConfiguration)\b'
|
|
68
|
+
r'|\bextends\s+SpringBootServletInitializer\b'
|
|
69
|
+
)
|
|
58
70
|
|
|
59
71
|
# --- Keycloak / Quarkus SPI ---
|
|
60
72
|
# Matches "implements EventListenerProvider" or "implements Foo, EventListenerProvider, Bar"
|
|
@@ -151,6 +163,18 @@ class JavaDetector(AbstractDetector):
|
|
|
151
163
|
# Spring profiles — check src/main/options/, src/main/resources/
|
|
152
164
|
spring_profiles = self._detect_spring_profiles(context.root, all_paths)
|
|
153
165
|
|
|
166
|
+
# BUG #4 (Alfresco field test): a `org.mybatis` coordinate in the pom is not
|
|
167
|
+
# proof the app USES MyBatis mappers. Alfresco declares org.mybatis:mybatis
|
|
168
|
+
# yet has zero @Mapper interfaces and zero *Mapper.xml — its SQL layer is
|
|
169
|
+
# iBatis-style *-SqlMap.xml (surfaced separately). Emitting "MyBatis" as an
|
|
170
|
+
# architecture framework then contradicted the tool's own evidence block
|
|
171
|
+
# (mapper_interfaces:0, xml_files:0) and mislabeled bean DTO mappers. Keep
|
|
172
|
+
# the label only with real usage evidence: a *Mapper.xml or an @Mapper interface.
|
|
173
|
+
if any(f.name == "MyBatis" for f in frameworks) and not self._has_mybatis_usage(
|
|
174
|
+
context.root, all_paths
|
|
175
|
+
):
|
|
176
|
+
frameworks = [f for f in frameworks if f.name != "MyBatis"]
|
|
177
|
+
|
|
154
178
|
entry_points = self._collect_entry_points(context)
|
|
155
179
|
transactional_classes = self._collect_transactional_classes(context, all_paths)
|
|
156
180
|
stack = StackDetection(
|
|
@@ -211,6 +235,17 @@ class JavaDetector(AbstractDetector):
|
|
|
211
235
|
break
|
|
212
236
|
break
|
|
213
237
|
|
|
238
|
+
# BUG #1 (Alfresco field test): a picked value can be a Maven property
|
|
239
|
+
# reference — Alfresco's <maven.compiler.source>${java.version}</...> chains
|
|
240
|
+
# to <java.version>21</java.version> in the SAME <properties> block. Resolve
|
|
241
|
+
# ${...} references against the pom's own properties so --compact reports the
|
|
242
|
+
# concrete version ("21") instead of the literal "${java.version}", matching
|
|
243
|
+
# what migrate-check already surfaces. Bounded iteration guards ref cycles.
|
|
244
|
+
if "language_version" in result:
|
|
245
|
+
result["language_version"] = _resolve_maven_property(
|
|
246
|
+
result["language_version"], props
|
|
247
|
+
)
|
|
248
|
+
|
|
214
249
|
return result
|
|
215
250
|
|
|
216
251
|
def _detect_spring_profiles(self, root: Path, all_paths: list[str]) -> list[str]:
|
|
@@ -425,15 +460,25 @@ class JavaDetector(AbstractDetector):
|
|
|
425
460
|
# Exclude test trees: test helpers like AdminApplication.java in
|
|
426
461
|
# integration/src/test/java/ must not be treated as production entrypoints.
|
|
427
462
|
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
463
|
+
# BUG #3 (Alfresco field test): a `*Application.java` / `*Main.java` NAME does
|
|
464
|
+
# not make a file a bootstrap entry point. Alfresco has XSD-generated JAXB
|
|
465
|
+
# `Application.java` model classes (no main(), no bootstrap annotation) in a
|
|
466
|
+
# WAR with no own entry point — flagging them as `application` polluted the
|
|
467
|
+
# executive summary. Emit `kind="application"` ONLY when the file actually
|
|
468
|
+
# declares a `main(String[])` method or a bootstrap annotation.
|
|
428
469
|
app_candidates = [
|
|
429
|
-
p for p in
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
entry_points: list[EntryPoint] = [
|
|
434
|
-
EntryPoint(path=p, stack="java", kind="application", source="manifest")
|
|
435
|
-
for p in unique_strings(app_candidates)
|
|
470
|
+
p for p in unique_strings([
|
|
471
|
+
q for q in all_java
|
|
472
|
+
if q.endswith(("Application.java", "Main.java")) and not _is_test_path(q)
|
|
473
|
+
])
|
|
436
474
|
]
|
|
475
|
+
entry_points: list[EntryPoint] = []
|
|
476
|
+
for p in app_candidates:
|
|
477
|
+
verified = self._verify_bootstrap_entry(context.root / p)
|
|
478
|
+
if verified:
|
|
479
|
+
entry_points.append(EntryPoint(
|
|
480
|
+
path=p, stack="java", kind="application", source=verified,
|
|
481
|
+
))
|
|
437
482
|
|
|
438
483
|
# 2. Annotation-based scan: @RestController, @WebFilter, FilterRegistrationBean
|
|
439
484
|
# Prioritize Controller-named files so all REST controllers are detected
|
|
@@ -481,6 +526,52 @@ class JavaDetector(AbstractDetector):
|
|
|
481
526
|
unique_eps.append(ep)
|
|
482
527
|
return unique_eps
|
|
483
528
|
|
|
529
|
+
def _has_mybatis_usage(self, root: Path, all_paths: list[str]) -> bool:
|
|
530
|
+
"""True when the repo shows real MyBatis usage, not just a pom coordinate.
|
|
531
|
+
|
|
532
|
+
Evidence = a *Mapper.xml file OR a *Mapper.java carrying the MyBatis
|
|
533
|
+
@Mapper annotation. iBatis-style *-SqlMap.xml is deliberately NOT counted
|
|
534
|
+
here — it is a distinct legacy pattern surfaced elsewhere (BUG #4).
|
|
535
|
+
"""
|
|
536
|
+
if any(p.endswith("Mapper.xml") for p in all_paths):
|
|
537
|
+
return True
|
|
538
|
+
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
539
|
+
mapper_java = [
|
|
540
|
+
p for p in all_paths
|
|
541
|
+
if p.endswith("Mapper.java") and not _is_test_path(p)
|
|
542
|
+
]
|
|
543
|
+
for rel in mapper_java[:_MAX_JAVA_ENTRY_SCAN]:
|
|
544
|
+
try:
|
|
545
|
+
abs_path = root / rel
|
|
546
|
+
if abs_path.stat().st_size > _MAX_FILE_SIZE:
|
|
547
|
+
continue
|
|
548
|
+
content = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
549
|
+
except OSError:
|
|
550
|
+
continue
|
|
551
|
+
if "org.apache.ibatis.annotations.Mapper" in content or "@Mapper" in content:
|
|
552
|
+
return True
|
|
553
|
+
return False
|
|
554
|
+
|
|
555
|
+
def _verify_bootstrap_entry(self, abs_path: Path) -> "str | None":
|
|
556
|
+
"""Return the evidence source when *abs_path* is a real bootstrap entry.
|
|
557
|
+
|
|
558
|
+
A file is a genuine application entry point only if it declares a
|
|
559
|
+
`main(String[])` method OR carries a recognized bootstrap annotation /
|
|
560
|
+
WAR initializer supertype. Returns "main_method" / "bootstrap_annotation"
|
|
561
|
+
accordingly, else None (name alone is never sufficient — see BUG #3).
|
|
562
|
+
"""
|
|
563
|
+
try:
|
|
564
|
+
if abs_path.stat().st_size > _MAX_FILE_SIZE:
|
|
565
|
+
return None
|
|
566
|
+
content = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
567
|
+
except OSError:
|
|
568
|
+
return None
|
|
569
|
+
if _BOOTSTRAP_ANNOTATION_RE.search(content):
|
|
570
|
+
return "bootstrap_annotation"
|
|
571
|
+
if _MAIN_METHOD_RE.search(content):
|
|
572
|
+
return "main_method"
|
|
573
|
+
return None
|
|
574
|
+
|
|
484
575
|
def _augment_deep_java_controllers(self, context: DetectionContext, all_java: list[str]) -> None:
|
|
485
576
|
"""Scan standard Java source roots for *Controller*.java files not in all_java.
|
|
486
577
|
|
sourcecode/detectors/parsers.py
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import re
|
|
4
5
|
from collections.abc import Iterable
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, Optional
|
|
7
8
|
|
|
9
|
+
_MAVEN_PROP_REF_RE = re.compile(r'\$\{([\w.\-]+)\}')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def substitute_maven_properties(text: str, props: "dict[str, str]") -> str:
|
|
13
|
+
"""Resolve Maven ${prop} references in *text* against *props*.
|
|
14
|
+
|
|
15
|
+
Shared single source of truth for Maven property resolution across
|
|
16
|
+
subcommands (BUG #1, Alfresco field test): both the --compact Java-stack
|
|
17
|
+
detector and migrate-check must resolve `${java.version}` → `21` identically.
|
|
18
|
+
Multi-level references (${a} where a=${b}) resolve in up to 3 passes; a
|
|
19
|
+
reference with no matching property is left verbatim (honest, not blanked).
|
|
20
|
+
"""
|
|
21
|
+
if not props or "${" not in text:
|
|
22
|
+
return text
|
|
23
|
+
resolved = text
|
|
24
|
+
for _ in range(3):
|
|
25
|
+
new = _MAVEN_PROP_REF_RE.sub(lambda m: props.get(m.group(1), m.group(0)), resolved)
|
|
26
|
+
if new == resolved:
|
|
27
|
+
break
|
|
28
|
+
resolved = new
|
|
29
|
+
return resolved
|
|
30
|
+
|
|
8
31
|
|
|
9
32
|
def load_json_file(path: Path) -> Optional[dict[str, Any]]:
|
|
10
33
|
"""Carga un fichero JSON sin lanzar si el contenido es invalido."""
|
|
@@ -101,12 +101,33 @@ _TOKEN_CLIENTS: "tuple[tuple[str, str, str], ...]" = (
|
|
|
101
101
|
# JMS / messaging
|
|
102
102
|
("JmsTemplate", "jms", "jmstemplate"),
|
|
103
103
|
("ActiveMQConnectionFactory", "jms", "activemq"),
|
|
104
|
+
# BUG #5 (Alfresco field test): the connection-factory family beyond the plain
|
|
105
|
+
# ActiveMQConnectionFactory — Alfresco wires ActiveMQSslConnectionFactory and
|
|
106
|
+
# pooled/caching factories as Spring @Beans, none of which were recognized.
|
|
107
|
+
("ActiveMQSslConnectionFactory", "jms", "activemq"),
|
|
108
|
+
("PooledConnectionFactory", "jms", "activemq-pooled"),
|
|
109
|
+
("CachingConnectionFactory", "jms", "spring-jms"),
|
|
104
110
|
)
|
|
105
111
|
_TOKEN_RES = tuple(
|
|
106
112
|
(re.compile(r"\b" + re.escape(tok) + r"\b"), kind, client)
|
|
107
113
|
for tok, kind, client in _TOKEN_CLIENTS
|
|
108
114
|
)
|
|
109
115
|
|
|
116
|
+
# BUG #5: Apache Camel route detection. Only applied inside a confirmed Camel file.
|
|
117
|
+
_CAMEL_CONTEXT_RE = re.compile(r"org\.apache\.camel\b|\bextends\s+RouteBuilder\b")
|
|
118
|
+
# A from()/to() endpoint with a literal scheme URI, e.g. from("activemq:queue:foo").
|
|
119
|
+
_CAMEL_URI_RE = re.compile(r'\b(from|to)\s*\(\s*"([a-z][a-z0-9+.\-]*):([^"]*)"')
|
|
120
|
+
# A route anchored by from(<non-string>) — endpoint URI comes from a variable/property.
|
|
121
|
+
_CAMEL_VAR_ROUTE_RE = re.compile(r'\bfrom\s*\(\s*[A-Za-z_]')
|
|
122
|
+
# Camel URI scheme → integration kind (jms-family messaging schemes collapse to "jms").
|
|
123
|
+
_CAMEL_SCHEME_KIND: "dict[str, str]" = {
|
|
124
|
+
"activemq": "jms", "amq": "jms", "jms": "jms", "sjms": "jms", "sjms2": "jms",
|
|
125
|
+
"amqp": "jms", "stomp": "jms",
|
|
126
|
+
"kafka": "kafka", "mqtt": "mqtt", "paho": "mqtt",
|
|
127
|
+
"http": "http", "https": "http", "http4": "http", "rest": "http",
|
|
128
|
+
"smtp": "smtp", "smtps": "smtp",
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
|
|
111
132
|
def _line_of(text: str, idx: int) -> int:
|
|
112
133
|
"""1-based line number of character offset ``idx`` in ``text``."""
|
|
@@ -323,6 +344,28 @@ def detect_integrations(file_paths: "list[str]", root: Path) -> dict:
|
|
|
323
344
|
_add(kind, client, url.group(1), rel, lineno)
|
|
324
345
|
break
|
|
325
346
|
|
|
347
|
+
# BUG #5 (Alfresco field test): Apache Camel routes are messaging/integration
|
|
348
|
+
# endpoints declared as a fluent DSL (from(...)/to(...)) inside a RouteBuilder,
|
|
349
|
+
# NOT as JmsTemplate/connection-factory constructs — so they were invisible.
|
|
350
|
+
# Only trust from()/to() inside a confirmed Camel file (import org.apache.camel
|
|
351
|
+
# or `extends RouteBuilder`) to avoid matching unrelated fluent `.to(...)`.
|
|
352
|
+
if _CAMEL_CONTEXT_RE.search(text):
|
|
353
|
+
for m in _CAMEL_URI_RE.finditer(text):
|
|
354
|
+
scheme = m.group(2).lower()
|
|
355
|
+
uri = f"{scheme}:{m.group(3)}"
|
|
356
|
+
kind = _CAMEL_SCHEME_KIND.get(scheme, "messaging")
|
|
357
|
+
_add(kind, f"camel-{scheme}", uri, rel, _line_of(text, m.start()))
|
|
358
|
+
# Routes whose endpoint URI is a variable/property (Alfresco's
|
|
359
|
+
# from(sourceQueue).to(targetTopic)) cannot yield a literal target, but the
|
|
360
|
+
# route IS a real messaging integration point — record it honestly with a
|
|
361
|
+
# null target and low confidence rather than dropping it.
|
|
362
|
+
if not any(r["client"].startswith("camel-") and r["evidence"].startswith(f"{rel}:")
|
|
363
|
+
for r in records):
|
|
364
|
+
vm = _CAMEL_VAR_ROUTE_RE.search(text)
|
|
365
|
+
if vm:
|
|
366
|
+
_add("messaging", "camel-route", None, rel,
|
|
367
|
+
_line_of(text, vm.start()), confidence="low")
|
|
368
|
+
|
|
326
369
|
records.sort(key=lambda r: (r["kind"], r["client"], r["evidence"]))
|
|
327
370
|
|
|
328
371
|
by_kind: "dict[str, int]" = {}
|
sourcecode/migrate_check.py
CHANGED
|
@@ -1168,18 +1168,10 @@ def _resolve_maven_properties(text: str) -> str:
|
|
|
1168
1168
|
props: dict[str, str] = {}
|
|
1169
1169
|
for m in re.finditer(r'<([A-Za-z][\w.\-]*)>\s*([^<${}]+?)\s*</\1>', text):
|
|
1170
1170
|
props[m.group(1)] = m.group(2).strip()
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
for _ in range(3):
|
|
1176
|
-
def _sub(m: re.Match) -> str: # noqa: E306
|
|
1177
|
-
return props.get(m.group(1), m.group(0))
|
|
1178
|
-
resolved_new = re.sub(r'\$\{([\w.\-]+)\}', _sub, resolved)
|
|
1179
|
-
if resolved_new == resolved:
|
|
1180
|
-
break
|
|
1181
|
-
resolved = resolved_new
|
|
1182
|
-
return resolved
|
|
1171
|
+
# Shared ${...} substitution core (BUG #1): identical resolution semantics as
|
|
1172
|
+
# the --compact Java-stack detector.
|
|
1173
|
+
from sourcecode.detectors.parsers import substitute_maven_properties
|
|
1174
|
+
return substitute_maven_properties(text, props)
|
|
1183
1175
|
|
|
1184
1176
|
|
|
1185
1177
|
def _scan_dep_file(text: str, rel_path: str) -> list["MigrationFinding"]:
|
sourcecode/repository_ir.py
CHANGED
|
@@ -4735,6 +4735,41 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
4735
4735
|
f"endpoints only."
|
|
4736
4736
|
)
|
|
4737
4737
|
result.setdefault("warnings", []).append(_msg)
|
|
4738
|
+
|
|
4739
|
+
# BUG #7 (Alfresco field test): a bare "total: 0" must never be read by an
|
|
4740
|
+
# automated consumer as "this service exposes no public API". Emit a first-class
|
|
4741
|
+
# structured `zero_result_reason` (mirroring integrations.coverage_note /
|
|
4742
|
+
# security_model_detail.reason) naming the routing patterns that WERE searched and
|
|
4743
|
+
# any non-Spring surface detected, so a 0 is understood as "no RECOGNIZED pattern",
|
|
4744
|
+
# not "no surface". Only when the Spring/annotation surface is genuinely empty.
|
|
4745
|
+
if not endpoints:
|
|
4746
|
+
_searched = (
|
|
4747
|
+
"@RestController/@Controller + @RequestMapping/@GetMapping/@PostMapping/"
|
|
4748
|
+
"@PutMapping/@DeleteMapping/@PatchMapping, JAX-RS @Path, functional "
|
|
4749
|
+
"RouterFunction routing, and OpenAPI-spec-recovered routes"
|
|
4750
|
+
)
|
|
4751
|
+
if _nonspring_total:
|
|
4752
|
+
_fw_names = {
|
|
4753
|
+
"webscripts": "Alfresco WebScripts (XML descriptor + handler class)",
|
|
4754
|
+
"jax_rs": "JAX-RS",
|
|
4755
|
+
"servlets": "mapped Servlets",
|
|
4756
|
+
}
|
|
4757
|
+
_detected = ", ".join(
|
|
4758
|
+
_fw_names.get(k, k) for k, v in _nonspring.items() if v
|
|
4759
|
+
)
|
|
4760
|
+
result["zero_result_reason"] = (
|
|
4761
|
+
f"0 endpoints recognized. Searched: {_searched}. A NON-Spring REST "
|
|
4762
|
+
f"surface WAS detected ({_detected}) but is not statically modeled — "
|
|
4763
|
+
f"do NOT read this as 'no public API'. Inspect that framework's routing "
|
|
4764
|
+
f"(e.g. *.desc.xml descriptors for WebScripts) to enumerate the surface."
|
|
4765
|
+
)
|
|
4766
|
+
else:
|
|
4767
|
+
result["zero_result_reason"] = (
|
|
4768
|
+
f"0 endpoints recognized. Searched: {_searched}. No recognized routing "
|
|
4769
|
+
f"construct was found; a framework this analyzer does not model may "
|
|
4770
|
+
f"still expose an HTTP surface — verify manually before concluding the "
|
|
4771
|
+
f"service has no public API."
|
|
4772
|
+
)
|
|
4738
4773
|
return result
|
|
4739
4774
|
|
|
4740
4775
|
|
sourcecode/serializer.py
CHANGED
|
@@ -702,7 +702,12 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
702
702
|
kind = getattr(ep, "kind", "")
|
|
703
703
|
stem = _Path(path).stem
|
|
704
704
|
|
|
705
|
-
|
|
705
|
+
# BUG #3 (Alfresco field test): bootstrap membership is driven by the
|
|
706
|
+
# VERIFIED entry kind ("application" is emitted only when the detector
|
|
707
|
+
# confirmed a main()/bootstrap annotation), never by a `*Application*`
|
|
708
|
+
# filename alone — XSD-generated Application.java model classes must not
|
|
709
|
+
# appear here.
|
|
710
|
+
if kind == "application":
|
|
706
711
|
if path not in seen_b:
|
|
707
712
|
seen_b.add(path)
|
|
708
713
|
bootstrap.append(path)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=5AfqhGbJzMplmuXdrkAIi2e7-XhaUzzC_uaeeU_260s,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=r_xf-SWXwUm3nVQMCSXHJ3M8zKsQP7Ze8Nqf6TVLRq8,45998
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=UaxmUU77oTlWxttMU3c6PxHrU-Q6q5N31TeU_NWWTdU,24297
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=IftvTlokcSLbZYHjXQu55FnS_X1LU9bXGsGxm6ujPtw,25793
|
|
8
8
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
9
9
|
sourcecode/classifier.py,sha256=YTTCoRdcLEFRVcql9Ow1dE7eYQj0jq2rgx32bRDnb1k,13852
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=UTcQWF3BGvEZ9I-gPyU9LYDGa-8AmNa_jQYO96hTAqw,288485
|
|
11
11
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
12
12
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
13
13
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -29,12 +29,12 @@ sourcecode/fqn_utils.py,sha256=XLU7zDkNBXz_RZkIUNfpPmp1nekWtqP-fxV92tDV1vg,2158
|
|
|
29
29
|
sourcecode/git_analyzer.py,sha256=JStxTQXNjBWi_wLdwhsZs9mT-v50cSJIz4Agzn6Kh9I,13362
|
|
30
30
|
sourcecode/graph_analyzer.py,sha256=DHR8fY69oU_Pi4SYaWboX6EoEFrctQKB9dsjpqwGMzw,62403
|
|
31
31
|
sourcecode/hibernate_strat.py,sha256=h0leIhlWvSjYq3F99LxvLIDLrJ-xPYxWAREG4LkqZ-4,61190
|
|
32
|
-
sourcecode/integration_detector.py,sha256=
|
|
32
|
+
sourcecode/integration_detector.py,sha256=9_SeZQXcatdoCfxl9ciSbRJ84IQkSJooAcv0Efh9FgM,21376
|
|
33
33
|
sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
34
34
|
sourcecode/license.py,sha256=wckiLuiwaE35KMCStUf1gYzleJuFe6qsSyxUQJvit3s,23500
|
|
35
35
|
sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
|
|
36
36
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
37
|
-
sourcecode/migrate_check.py,sha256=
|
|
37
|
+
sourcecode/migrate_check.py,sha256=Fz12IrMTQWjvbT7melwJGf1aNqQDtzbyRPd_bDA7hdw,105025
|
|
38
38
|
sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
|
|
39
39
|
sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
|
|
40
40
|
sourcecode/path_filters.py,sha256=VnaD9jxZVNzluackNSTCdIddwzAqIseuSCs_A-gpCDM,6898
|
|
@@ -47,14 +47,14 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
|
47
47
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
48
48
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
49
49
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
50
|
-
sourcecode/repository_ir.py,sha256=
|
|
50
|
+
sourcecode/repository_ir.py,sha256=YUuyWIFeUve26RGaWPIk1Qbj04u6sTyR1-qPs-gYznM,245995
|
|
51
51
|
sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
|
|
52
52
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
53
53
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
54
54
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
55
55
|
sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs,3503
|
|
56
56
|
sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIxkw,89176
|
|
57
|
-
sourcecode/serializer.py,sha256=
|
|
57
|
+
sourcecode/serializer.py,sha256=hv5pQQQa20XxPwcKLlLAgvXwWubm-B-cgQept6x-H7U,129373
|
|
58
58
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
59
59
|
sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
|
|
60
60
|
sourcecode/spring_impact.py,sha256=1Eu1vkdwTVsw92iBEJDe_i_FaSf4uGH9Rb0eSgIAAZc,57542
|
|
@@ -76,10 +76,10 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
|
|
|
76
76
|
sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
|
|
77
77
|
sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
|
|
78
78
|
sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
|
|
79
|
-
sourcecode/detectors/java.py,sha256=
|
|
79
|
+
sourcecode/detectors/java.py,sha256=xlp4mqd26QWJf3MWqmTbD0IND_VEVzPHlGaLC4MNXNA,39028
|
|
80
80
|
sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
|
|
81
81
|
sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
|
|
82
|
-
sourcecode/detectors/parsers.py,sha256=
|
|
82
|
+
sourcecode/detectors/parsers.py,sha256=ug9K31tyHqinmv0HkIVQVjdTZpBv67FYKAEf52YXOSM,3178
|
|
83
83
|
sourcecode/detectors/php.py,sha256=W_AQD0WMVDdWHa9h_ilX6W8XSpz0X4ctpMK2WXfXf1I,1887
|
|
84
84
|
sourcecode/detectors/project.py,sha256=ghWWOlqg2_uywzeZX573CCkFWQPlc8bBGvvX1BfIDYI,8315
|
|
85
85
|
sourcecode/detectors/python.py,sha256=i2_Wtk_p0BJx5R8gBQ8NaQByzJ8zEfZkw9NNpKlvOYM,10486
|
|
@@ -104,8 +104,8 @@ sourcecode/telemetry/consent.py,sha256=H5z2Wu63pZqbaKucRPoJQJ0zCo4cke9ZlBrJC-MaW
|
|
|
104
104
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
105
105
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
106
106
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
109
|
-
sourcecode-1.
|
|
110
|
-
sourcecode-1.
|
|
111
|
-
sourcecode-1.
|
|
107
|
+
sourcecode-1.73.0.dist-info/METADATA,sha256=s089V_1fTpsg8m9aRjhv4mv0miXocG_qV5uAyzr2L60,48339
|
|
108
|
+
sourcecode-1.73.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
109
|
+
sourcecode-1.73.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
110
|
+
sourcecode-1.73.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
111
|
+
sourcecode-1.73.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|