sourcecode 1.64.0__py3-none-any.whl → 1.66.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/hibernate_strat.py +102 -2
- sourcecode/integration_detector.py +31 -0
- sourcecode/migrate_check.py +320 -77
- sourcecode/path_filters.py +33 -0
- sourcecode/serializer.py +54 -6
- {sourcecode-1.64.0.dist-info → sourcecode-1.66.0.dist-info}/METADATA +45 -3
- {sourcecode-1.64.0.dist-info → sourcecode-1.66.0.dist-info}/RECORD +11 -11
- {sourcecode-1.64.0.dist-info → sourcecode-1.66.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.64.0.dist-info → sourcecode-1.66.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.64.0.dist-info → sourcecode-1.66.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/hibernate_strat.py
CHANGED
|
@@ -253,6 +253,75 @@ def _scan_build_dependency(root: Path) -> tuple[bool, list[str]]:
|
|
|
253
253
|
samples.append(snippet)
|
|
254
254
|
return found, samples
|
|
255
255
|
|
|
256
|
+
|
|
257
|
+
# ── BUG #1: effective Hibernate version resolution ─────────────────────────
|
|
258
|
+
# A 5→6 rewrite verdict is only valid when the project is actually on Hibernate 5.
|
|
259
|
+
# Resolve the effective major version from build descriptors, following Maven
|
|
260
|
+
# ${properties} (incl. BOM-managed values declared as <hibernate*.version>) and
|
|
261
|
+
# explicit hibernate-core / hibernate-orm coordinates. When the resolved major is
|
|
262
|
+
# ≥ 6 the 5→6 axis does NOT apply (the migration is already done). When it cannot
|
|
263
|
+
# be resolved the axis degrades to a low-confidence hypothesis (no headline).
|
|
264
|
+
_HIB_VER = r"(\d+\.\d+(?:\.\d+)?(?:[.\-][\w]+)*)" # 6.2.13.Final, 5.6.15, 6.2
|
|
265
|
+
_HIB_PROP_RE = re.compile(
|
|
266
|
+
r"<((?:[\w.\-]*hibernate[\w.\-]*?)\.?version)>\s*" + _HIB_VER + r"\s*</\1>",
|
|
267
|
+
re.IGNORECASE,
|
|
268
|
+
)
|
|
269
|
+
_HIB_COORD_VERSION_RE = re.compile(
|
|
270
|
+
r"<artifactId>\s*hibernate-(?:core|orm)\s*</artifactId>\s*"
|
|
271
|
+
r"(?:<[^>]+>[^<]*</[^>]+>\s*)*?<version>\s*" + _HIB_VER,
|
|
272
|
+
re.IGNORECASE | re.DOTALL,
|
|
273
|
+
)
|
|
274
|
+
_HIB_GRADLE_VERSION_RE = re.compile(
|
|
275
|
+
r"org\.hibernate(?:\.orm)?:hibernate-(?:core|orm):" + _HIB_VER,
|
|
276
|
+
re.IGNORECASE,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _ver_key(full: str) -> tuple[int, int]:
|
|
281
|
+
parts = full.split(".")
|
|
282
|
+
try:
|
|
283
|
+
major = int(parts[0])
|
|
284
|
+
except (ValueError, IndexError):
|
|
285
|
+
return (0, 0)
|
|
286
|
+
try:
|
|
287
|
+
minor = int(parts[1]) if len(parts) > 1 else 0
|
|
288
|
+
except ValueError:
|
|
289
|
+
minor = 0
|
|
290
|
+
return (major, minor)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _resolve_hibernate_version(root: Path) -> tuple[Optional[str], Optional[int], str]:
|
|
294
|
+
"""Return (full_version_string, major, confidence) for the effective Hibernate.
|
|
295
|
+
|
|
296
|
+
confidence is "high" when a version was resolved from a build descriptor,
|
|
297
|
+
"none" when none could be determined (degrade to hypothesis). The newest
|
|
298
|
+
declared Hibernate wins in a multi-module build.
|
|
299
|
+
"""
|
|
300
|
+
import os
|
|
301
|
+
best_full: Optional[str] = None
|
|
302
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
303
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP_BUILD_SCAN_DIRS]
|
|
304
|
+
for fname in filenames:
|
|
305
|
+
if fname not in _BUILD_FILE_NAMES:
|
|
306
|
+
continue
|
|
307
|
+
try:
|
|
308
|
+
text = (Path(dirpath) / fname).read_text(encoding="utf-8", errors="replace")
|
|
309
|
+
except OSError:
|
|
310
|
+
continue
|
|
311
|
+
fulls: list[str] = []
|
|
312
|
+
for m in _HIB_PROP_RE.finditer(text):
|
|
313
|
+
fulls.append(m.group(2))
|
|
314
|
+
for m in _HIB_COORD_VERSION_RE.finditer(text):
|
|
315
|
+
fulls.append(m.group(1))
|
|
316
|
+
for m in _HIB_GRADLE_VERSION_RE.finditer(text):
|
|
317
|
+
fulls.append(m.group(1))
|
|
318
|
+
for full in fulls:
|
|
319
|
+
if best_full is None or _ver_key(full) > _ver_key(best_full):
|
|
320
|
+
best_full = full
|
|
321
|
+
if best_full is None:
|
|
322
|
+
return None, None, "none"
|
|
323
|
+
return best_full, _ver_key(best_full)[0], "high"
|
|
324
|
+
|
|
256
325
|
# Escalation markers — dynamic / reflection-based persistence construction.
|
|
257
326
|
_ABSTRACTION_CLASS_RE = re.compile(
|
|
258
327
|
r"\b(DynamicEntityDao|DynamicEntityDaoImpl|BasicPersistenceModule|"
|
|
@@ -406,12 +475,23 @@ class HibernateStratification:
|
|
|
406
475
|
# when backed by a build dependency or a parsed org.hibernate import, "none"
|
|
407
476
|
# when detection was vetoed for lack of evidence.
|
|
408
477
|
evidence: dict = field(default_factory=dict)
|
|
478
|
+
# BUG #1: effective Hibernate version + whether the 5→6 axis applies at all.
|
|
479
|
+
# When the resolved major is ≥ 6 the migration is already done → not applicable
|
|
480
|
+
# (never a blocker). When unresolved, the axis is a low-confidence hypothesis.
|
|
481
|
+
effective_version: Optional[str] = None
|
|
482
|
+
version_major: Optional[int] = None
|
|
483
|
+
version_confidence: str = "none"
|
|
484
|
+
migration_applicable: bool = True
|
|
409
485
|
|
|
410
486
|
def to_dict(self) -> dict:
|
|
411
487
|
return {
|
|
412
488
|
"schema_version": HIBERNATE_SCHEMA_VERSION,
|
|
413
489
|
"detected": self.detected,
|
|
414
490
|
"evidence": self.evidence,
|
|
491
|
+
"effective_version": self.effective_version,
|
|
492
|
+
"version_major": self.version_major,
|
|
493
|
+
"version_confidence": self.version_confidence,
|
|
494
|
+
"migration_applicable": self.migration_applicable,
|
|
415
495
|
"classification": self.classification,
|
|
416
496
|
"classification_label": self.classification_label,
|
|
417
497
|
"stratified": True,
|
|
@@ -763,6 +843,7 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
|
|
|
763
843
|
return strat
|
|
764
844
|
|
|
765
845
|
dep_present, dep_samples = _scan_build_dependency(root)
|
|
846
|
+
eff_version, version_major, version_conf = _resolve_hibernate_version(root)
|
|
766
847
|
|
|
767
848
|
has_evidence = dep_present or any_hibernate_import or any_jpa_import
|
|
768
849
|
evidence: dict = {
|
|
@@ -771,6 +852,9 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
|
|
|
771
852
|
"hibernate_import_present": any_hibernate_import,
|
|
772
853
|
"hibernate_import_sample": hibernate_import_sample,
|
|
773
854
|
"jpa_import_present": any_jpa_import,
|
|
855
|
+
"effective_version": eff_version,
|
|
856
|
+
"version_major": version_major,
|
|
857
|
+
"version_confidence": version_conf,
|
|
774
858
|
"confidence": "high" if has_evidence else "none",
|
|
775
859
|
}
|
|
776
860
|
|
|
@@ -959,6 +1043,12 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
|
|
|
959
1043
|
strat = HibernateStratification()
|
|
960
1044
|
strat.findings = findings
|
|
961
1045
|
strat.evidence = evidence
|
|
1046
|
+
strat.effective_version = eff_version
|
|
1047
|
+
strat.version_major = version_major
|
|
1048
|
+
strat.version_confidence = version_conf
|
|
1049
|
+
# BUG #1: the 5→6 axis only applies when NOT already on Hibernate 6+. A
|
|
1050
|
+
# resolved major ≥ 6 means the migration is complete — never a blocker.
|
|
1051
|
+
strat.migration_applicable = not (version_major is not None and version_major >= 6)
|
|
962
1052
|
strat.rewrite_targets = sorted(
|
|
963
1053
|
targets, key=lambda t: (t.source_file, t.line_start, t.layer, t.id)
|
|
964
1054
|
)
|
|
@@ -1126,13 +1216,23 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
|
|
|
1126
1216
|
stops.append("SQL/HQL shape not statically inferable (string concatenation)")
|
|
1127
1217
|
strat.stop_conditions_triggered = stops
|
|
1128
1218
|
|
|
1129
|
-
if
|
|
1219
|
+
if not strat.migration_applicable:
|
|
1220
|
+
# Already on Hibernate 6+ — the 5→6 stratification below is retained as an
|
|
1221
|
+
# informational exposure map, but it is NOT a migration and NOT a blocker.
|
|
1222
|
+
strat.classification = CLASS_NONE
|
|
1223
|
+
strat.classification_label = (
|
|
1224
|
+
f"ALREADY ON HIBERNATE {strat.version_major} "
|
|
1225
|
+
f"({strat.effective_version}) — NO 5→6 MIGRATION PENDING"
|
|
1226
|
+
)
|
|
1227
|
+
strat.stop_conditions_triggered = []
|
|
1228
|
+
elif stops:
|
|
1130
1229
|
strat.classification = CLASS_REWRITE
|
|
1131
1230
|
elif layer_files[LAYER_CRITERIA] or concat_query_global or jpa_deprecated_seen:
|
|
1132
1231
|
strat.classification = CLASS_UPGRADE_CARE
|
|
1133
1232
|
else:
|
|
1134
1233
|
strat.classification = CLASS_UPGRADE
|
|
1135
|
-
|
|
1234
|
+
if strat.migration_applicable:
|
|
1235
|
+
strat.classification_label = _CLASS_LABELS[strat.classification]
|
|
1136
1236
|
|
|
1137
1237
|
# ── Risk separation: observable vs inferred ─────────────────────────────
|
|
1138
1238
|
observable: list[str] = []
|
|
@@ -35,10 +35,28 @@ _ATTR_NAME_RE = re.compile(r'(?:name|value)\s*=\s*"([^"]*)"')
|
|
|
35
35
|
_FIRST_LITERAL_RE = re.compile(r'^\s*"([^"]*)"')
|
|
36
36
|
|
|
37
37
|
# token -> (kind, client). Matched as whole-word usage outside imports/comments.
|
|
38
|
+
# Covers Spring AND plain-Java/Jakarta stacks (Quarkus, Micronaut, Keycloak SPI):
|
|
39
|
+
# the detector must not be Spring-centric, or a non-Spring repo with real LDAP /
|
|
40
|
+
# SMTP / HTTP integrations falsely reports "0 integrations".
|
|
38
41
|
_TOKEN_CLIENTS: "tuple[tuple[str, str, str], ...]" = (
|
|
42
|
+
# Spring HTTP clients
|
|
39
43
|
("RestTemplate", "http", "resttemplate"),
|
|
40
44
|
("WebClient", "http", "webclient"),
|
|
45
|
+
# Plain-Java / third-party HTTP clients
|
|
46
|
+
("HttpClient", "http", "jdk-httpclient"), # java.net.http.HttpClient
|
|
47
|
+
("CloseableHttpClient", "http", "apache-httpclient"),
|
|
48
|
+
("HttpClients", "http", "apache-httpclient"),
|
|
49
|
+
("OkHttpClient", "http", "okhttp"),
|
|
50
|
+
# LDAP / directory (Spring + JNDI)
|
|
41
51
|
("LdapTemplate", "ldap", "ldaptemplate"),
|
|
52
|
+
("InitialLdapContext", "ldap", "jndi-ldap"),
|
|
53
|
+
("InitialDirContext", "ldap", "jndi-ldap"),
|
|
54
|
+
("LdapContext", "ldap", "jndi-ldap"),
|
|
55
|
+
# Mail / SMTP (JavaMail / Jakarta Mail)
|
|
56
|
+
("JavaMailSender", "smtp", "spring-mail"),
|
|
57
|
+
("MimeMessage", "smtp", "javamail"),
|
|
58
|
+
("Transport", "smtp", "javamail"),
|
|
59
|
+
# JMS / messaging
|
|
42
60
|
("JmsTemplate", "jms", "jmstemplate"),
|
|
43
61
|
("ActiveMQConnectionFactory", "jms", "activemq"),
|
|
44
62
|
)
|
|
@@ -156,8 +174,21 @@ def detect_integrations(file_paths: "list[str]", root: Path) -> dict:
|
|
|
156
174
|
for r in records:
|
|
157
175
|
by_kind[r["kind"]] = by_kind.get(r["kind"], 0) + 1
|
|
158
176
|
|
|
177
|
+
# BUG #9: honest confidence. A zero count means "no detectable client
|
|
178
|
+
# construct was found", NOT "this system has no integrations" — runtime-wired
|
|
179
|
+
# clients (DI, config-driven endpoints, JCA connectors) are invisible to static
|
|
180
|
+
# text matching. Report that explicitly instead of an authoritative "0".
|
|
181
|
+
confidence = "observed" if records else "not_analyzed"
|
|
159
182
|
return {
|
|
160
183
|
"integrations": records,
|
|
161
184
|
"by_kind": {k: by_kind[k] for k in sorted(by_kind)},
|
|
162
185
|
"count": len(records),
|
|
186
|
+
"confidence": confidence,
|
|
187
|
+
"coverage_note": (
|
|
188
|
+
"Detects HTTP (RestTemplate/WebClient/JDK/Apache/OkHttp), LDAP (Spring "
|
|
189
|
+
"+ JNDI), SMTP (JavaMail), and JMS client constructs by source-text "
|
|
190
|
+
"matching. A count of 0 means no such construct was found, not that the "
|
|
191
|
+
"system has no outbound integrations — runtime/DI-wired clients are not "
|
|
192
|
+
"statically visible."
|
|
193
|
+
),
|
|
163
194
|
}
|
sourcecode/migrate_check.py
CHANGED
|
@@ -22,6 +22,8 @@ from datetime import datetime, timezone
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Optional, TYPE_CHECKING
|
|
24
24
|
|
|
25
|
+
from sourcecode.path_filters import is_test_or_fixture_path
|
|
26
|
+
|
|
25
27
|
if TYPE_CHECKING:
|
|
26
28
|
from sourcecode.hibernate_strat import HibernateStratification
|
|
27
29
|
|
|
@@ -582,6 +584,54 @@ def _is_no_migrate_javax(fqn: str) -> bool:
|
|
|
582
584
|
"""True if a javax FQN belongs to a JDK/permanent namespace (no jakarta move)."""
|
|
583
585
|
return any(fqn.startswith(p) for p in _JAKARTA_NO_MIGRATE_PREFIXES)
|
|
584
586
|
|
|
587
|
+
|
|
588
|
+
# BUG #8: autogenerated source markers — path fragments and the JSR-250 marker.
|
|
589
|
+
_GENERATED_PATH_FRAGMENTS: tuple[str, ...] = (
|
|
590
|
+
"/generated-sources/", "/generated/", "/target/generated",
|
|
591
|
+
"/build/generated", "/.apt_generated/",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _classify_code_context(finding: "MigrationFinding") -> str:
|
|
596
|
+
"""Bucket a finding as main / test / generated for blocking-count segregation."""
|
|
597
|
+
path = finding.source_file
|
|
598
|
+
if is_test_or_fixture_path(path):
|
|
599
|
+
return "test"
|
|
600
|
+
norm = path.replace("\\", "/").lower()
|
|
601
|
+
if any(frag in norm for frag in _GENERATED_PATH_FRAGMENTS):
|
|
602
|
+
return "generated"
|
|
603
|
+
# javax.annotation.Generated is the marker emitted into autogenerated code.
|
|
604
|
+
if finding.rule_id == "MIG-006" and finding.imports_found and all(
|
|
605
|
+
imp.rsplit(".", 1)[-1] == "Generated" for imp in finding.imports_found
|
|
606
|
+
):
|
|
607
|
+
return "generated"
|
|
608
|
+
return "main"
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# BUG #2: Spring presence signal. The Boot3 dimension only applies to repos that
|
|
612
|
+
# actually use Spring — a Quarkus/Micronaut/Helidon/Jakarta-pure repo has no
|
|
613
|
+
# Spring Boot 2→3 axis. Detected from build coordinates or framework imports.
|
|
614
|
+
_SPRING_BUILD_RE: re.Pattern = re.compile(
|
|
615
|
+
r"org\.springframework(?:\.boot)?\b|spring-boot-starter", re.IGNORECASE
|
|
616
|
+
)
|
|
617
|
+
_SPRING_IMPORT_RE: re.Pattern = re.compile(
|
|
618
|
+
r"^[ \t]*import\s+org\.springframework\.", re.MULTILINE
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _detect_spring_present(root: Path, spring_import_seen: bool) -> bool:
|
|
623
|
+
"""True when Spring is actually used (build coordinate or org.springframework import)."""
|
|
624
|
+
if spring_import_seen:
|
|
625
|
+
return True
|
|
626
|
+
for abs_path, _rel in _find_build_files(root):
|
|
627
|
+
try:
|
|
628
|
+
text = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
629
|
+
except OSError:
|
|
630
|
+
continue
|
|
631
|
+
if _SPRING_BUILD_RE.search(text):
|
|
632
|
+
return True
|
|
633
|
+
return False
|
|
634
|
+
|
|
585
635
|
# G-1: cap on total readiness deduction from low-severity (advisory, non-blocking)
|
|
586
636
|
# findings, so optional modernization cleanups cannot collapse the migration-readiness
|
|
587
637
|
# headline on a repo with zero blockers. See MigrationReport.finalize.
|
|
@@ -594,6 +644,23 @@ _JAKARTA_TARGETS: frozenset[str] = frozenset({"jakarta"})
|
|
|
594
644
|
_BOOT3_MIGRATION_TARGETS: frozenset[str] = frozenset(
|
|
595
645
|
{"jakarta", "spring_boot_3", "spring_security_6"}
|
|
596
646
|
)
|
|
647
|
+
# BUG #6: best-practice hygiene (java.util.Date → java.time) blocks NO version
|
|
648
|
+
# upgrade. It is advisory only and must never sink a readiness dimension to 0 —
|
|
649
|
+
# reported as a separate hygiene metric, excluded from JDK-modernization scoring.
|
|
650
|
+
_BEST_PRACTICE_TARGETS: frozenset[str] = frozenset({"java_8_best_practice"})
|
|
651
|
+
|
|
652
|
+
# BUG #3: the migration dimensions that feed the readiness_score aggregate, in
|
|
653
|
+
# order. jdk_modernization is deliberately NOT here — it is orthogonal upkeep debt
|
|
654
|
+
# reported on its own axis, never folded into the migration headline.
|
|
655
|
+
_MIGRATION_DIMENSIONS: tuple[str, ...] = ("jakarta", "boot3", "hibernate")
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _parse_major(version: "Optional[str]") -> "Optional[int]":
|
|
659
|
+
"""Leading integer of a version string ('4.0.3' → 4); None when not parseable."""
|
|
660
|
+
if not version:
|
|
661
|
+
return None
|
|
662
|
+
m = re.match(r"\s*(\d+)", version)
|
|
663
|
+
return int(m.group(1)) if m else None
|
|
597
664
|
# Cap on total readiness deduction from JDK-modernization findings (medium/low),
|
|
598
665
|
# so reflection/date cleanups cannot collapse a jakarta-ready repo's headline.
|
|
599
666
|
_JDK_ADVISORY_DEDUCTION_CAP: int = 15
|
|
@@ -1013,6 +1080,10 @@ class MigrationFinding:
|
|
|
1013
1080
|
fix_hint: str = ""
|
|
1014
1081
|
migration_target: str = ""
|
|
1015
1082
|
openrewrite_recipe: Optional[str] = None
|
|
1083
|
+
# BUG #8: where the finding lives — "main" (product), "test" (test/fixture
|
|
1084
|
+
# harness), or "generated" (autogenerated source). Only "main" counts toward
|
|
1085
|
+
# blocking_count / readiness; test+generated are reported in a separate bucket.
|
|
1086
|
+
code_context: str = "main"
|
|
1016
1087
|
|
|
1017
1088
|
@staticmethod
|
|
1018
1089
|
def make_id(rule_id: str, source_file: str) -> str:
|
|
@@ -1031,6 +1102,7 @@ class MigrationFinding:
|
|
|
1031
1102
|
"fix_hint": self.fix_hint,
|
|
1032
1103
|
"migration_target": self.migration_target,
|
|
1033
1104
|
"auto_fix_available": bool(self.openrewrite_recipe),
|
|
1105
|
+
"code_context": self.code_context,
|
|
1034
1106
|
}
|
|
1035
1107
|
if self.imports_found:
|
|
1036
1108
|
d["imports_found"] = self.imports_found
|
|
@@ -1061,8 +1133,12 @@ def _dimension_score(
|
|
|
1061
1133
|
low: set[str] = set()
|
|
1062
1134
|
for f in findings:
|
|
1063
1135
|
if targets is None:
|
|
1136
|
+
# JDK-modernization axis: everything NOT a Boot 2→3 blocker, EXCEPT
|
|
1137
|
+
# best-practice hygiene (BUG #6) which is advisory and scored separately.
|
|
1064
1138
|
if f.migration_target in _BOOT3_MIGRATION_TARGETS:
|
|
1065
1139
|
continue
|
|
1140
|
+
if f.migration_target in _BEST_PRACTICE_TARGETS:
|
|
1141
|
+
continue
|
|
1066
1142
|
elif f.migration_target not in targets:
|
|
1067
1143
|
continue
|
|
1068
1144
|
if f.severity == "critical":
|
|
@@ -1107,12 +1183,22 @@ class MigrationReport:
|
|
|
1107
1183
|
# is excluded from the aggregate — it is never counted as 0. Maps dimension →
|
|
1108
1184
|
# {"applicable": bool, "score": int|None, "reason": str}.
|
|
1109
1185
|
applicable_dimensions: dict = field(default_factory=dict)
|
|
1186
|
+
# BUG #3: how readiness_score was derived (method + the exact applicable
|
|
1187
|
+
# dimension scores it aggregates) so the headline number is fully traceable.
|
|
1188
|
+
readiness_aggregate: dict = field(default_factory=dict)
|
|
1110
1189
|
blocking_count: int = 0
|
|
1111
1190
|
estimated_effort_days: float = 0.0
|
|
1112
1191
|
# Tri-state: True = Boot 2 confirmed, False = Boot 3+ confirmed,
|
|
1113
1192
|
# None = could not determine. Absence of evidence is never reported as True.
|
|
1114
1193
|
spring_boot_2_detected: Optional[bool] = None
|
|
1115
1194
|
spring_boot_version_detected: Optional[str] = None
|
|
1195
|
+
# BUG #2: whether Spring is used at all. The Boot3 dimension is N/A without it.
|
|
1196
|
+
spring_present: bool = True
|
|
1197
|
+
# BUG #6 / #8: findings that do NOT count toward blocking_count or readiness —
|
|
1198
|
+
# best-practice hygiene (java.util.Date…) and test/fixture/generated buckets,
|
|
1199
|
+
# surfaced separately so the headline reflects real product migration risk.
|
|
1200
|
+
hygiene_findings: int = 0
|
|
1201
|
+
non_blocking: dict = field(default_factory=dict)
|
|
1116
1202
|
|
|
1117
1203
|
findings: list[MigrationFinding] = field(default_factory=list)
|
|
1118
1204
|
limitations: list[str] = field(default_factory=list)
|
|
@@ -1138,82 +1224,148 @@ class MigrationReport:
|
|
|
1138
1224
|
by_target[f.migration_target] = by_target.get(f.migration_target, 0) + 1
|
|
1139
1225
|
affected_files.add(f.source_file)
|
|
1140
1226
|
|
|
1141
|
-
|
|
1227
|
+
# BUG #8: only product ("main") code counts toward blocking_count, the
|
|
1228
|
+
# readiness dimensions, and effort. Test harnesses/fixtures (deprecated
|
|
1229
|
+
# web.xml descriptors, test-only sun.* HTTP servers) and autogenerated
|
|
1230
|
+
# sources are reported in a separate non-blocking bucket — counting them
|
|
1231
|
+
# as product blockers inflates the verdict.
|
|
1232
|
+
main_findings = [f for f in self.findings if f.code_context == "main"]
|
|
1233
|
+
|
|
1234
|
+
def _files_by_sev(items: list["MigrationFinding"], sev: str,
|
|
1235
|
+
targets: "Optional[frozenset[str]]" = None,
|
|
1236
|
+
exclude: "Optional[frozenset[str]]" = None) -> set[str]:
|
|
1237
|
+
out: set[str] = set()
|
|
1238
|
+
for f in items:
|
|
1239
|
+
if f.severity != sev:
|
|
1240
|
+
continue
|
|
1241
|
+
if targets is not None and f.migration_target not in targets:
|
|
1242
|
+
continue
|
|
1243
|
+
if exclude is not None and f.migration_target in exclude:
|
|
1244
|
+
continue
|
|
1245
|
+
out.add(f.source_file)
|
|
1246
|
+
return out
|
|
1247
|
+
|
|
1248
|
+
critical_files = _files_by_sev(main_findings, "critical")
|
|
1249
|
+
high_files = _files_by_sev(main_findings, "high")
|
|
1250
|
+
medium_files = _files_by_sev(main_findings, "medium")
|
|
1251
|
+
low_files = _files_by_sev(main_findings, "low")
|
|
1252
|
+
|
|
1253
|
+
self.blocking_count = sum(
|
|
1254
|
+
1 for f in main_findings if f.severity in ("critical", "high")
|
|
1255
|
+
)
|
|
1142
1256
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1257
|
+
# Per-dimension readiness — independent severity-weighted scores (MAIN only).
|
|
1258
|
+
self.jakarta_readiness = _dimension_score(main_findings, _JAKARTA_TARGETS)
|
|
1259
|
+
self.boot3_readiness = _dimension_score(main_findings, _BOOT3_MIGRATION_TARGETS)
|
|
1260
|
+
self.jdk_modernization = _dimension_score(main_findings, None)
|
|
1261
|
+
if not self.spring_present:
|
|
1262
|
+
self.boot3_readiness = 100
|
|
1263
|
+
|
|
1264
|
+
# ── BUG #2: Hibernate applicability — version-driven, never heuristic ────
|
|
1265
|
+
# The 5→6 axis is applicable ONLY when the repo is actually on Hibernate < 6.
|
|
1266
|
+
# resolved < 6 → applicable (rewrite score is a measurement)
|
|
1267
|
+
# resolved ≥ 6 → N/A (already migrated)
|
|
1268
|
+
# unresolved, Boot≥3 → N/A (Boot 3/4 BOM manages Hibernate ORM ≥6)
|
|
1269
|
+
# unresolved, Boot==2 → applicable (Boot 2 BOM manages Hibernate 5.x)
|
|
1270
|
+
# unresolved, no BOM → N/A, status "unresolved" (NEVER a heuristic number
|
|
1271
|
+
# that looks like a measurement on absent data)
|
|
1272
|
+
hib = self.hibernate
|
|
1273
|
+
hib_detected = hib is not None and hib.detected
|
|
1274
|
+
boot_major = _parse_major(self.spring_boot_version_detected)
|
|
1275
|
+
_hibernate_applies = False
|
|
1276
|
+
_hib_score: Optional[int] = None
|
|
1277
|
+
hib_status = "not_detected"
|
|
1278
|
+
_hib_reason = "N/A — no Hibernate dependency or import detected"
|
|
1279
|
+
if hib_detected:
|
|
1280
|
+
if hib.version_confidence == "high":
|
|
1281
|
+
if hib.migration_applicable: # resolved major < 6
|
|
1282
|
+
_hibernate_applies, _hib_score = True, hib.readiness
|
|
1283
|
+
hib_status = "resolved_h5"
|
|
1284
|
+
_hib_reason = f"Hibernate 5→6 rewrite axis (resolved {hib.effective_version})"
|
|
1285
|
+
else: # resolved major ≥ 6
|
|
1286
|
+
hib_status = "managed_ge6"
|
|
1287
|
+
_hib_reason = (f"N/A — resolved Hibernate {hib.effective_version} (≥6); "
|
|
1288
|
+
f"no 5→6 migration pending")
|
|
1289
|
+
elif boot_major is not None and boot_major >= 3:
|
|
1290
|
+
hib_status = "managed_ge6"
|
|
1291
|
+
_hib_reason = (f"N/A — Hibernate version managed by Spring Boot "
|
|
1292
|
+
f"{self.spring_boot_version_detected} BOM (Hibernate ORM ≥6); "
|
|
1293
|
+
f"5→6 axis inapplicable")
|
|
1294
|
+
elif boot_major == 2:
|
|
1295
|
+
_hibernate_applies, _hib_score = True, hib.readiness
|
|
1296
|
+
hib_status = "managed_h5"
|
|
1297
|
+
_hib_reason = (f"Hibernate 5→6 rewrite axis (Hibernate 5.x managed by Spring "
|
|
1298
|
+
f"Boot {self.spring_boot_version_detected} BOM)")
|
|
1154
1299
|
else:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
#
|
|
1159
|
-
#
|
|
1160
|
-
|
|
1161
|
-
#
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
deduction = (
|
|
1174
|
-
len(critical_files) * 15
|
|
1175
|
-
+ len(high_files) * 8
|
|
1176
|
-
+ len(mig_med) * 3
|
|
1177
|
-
+ min(len(mig_low) * 1, _LOW_SEVERITY_DEDUCTION_CAP)
|
|
1178
|
-
+ min(len(jdk_med) * 3 + len(jdk_low) * 1, _JDK_ADVISORY_DEDUCTION_CAP)
|
|
1179
|
-
)
|
|
1180
|
-
self.readiness_score = max(0, 100 - deduction)
|
|
1181
|
-
|
|
1182
|
-
# Per-dimension readiness — independent severity-weighted scores so the
|
|
1183
|
-
# output reveals that jakarta/Boot3 may be complete even with JDK debt.
|
|
1184
|
-
self.jakarta_readiness = _dimension_score(self.findings, _JAKARTA_TARGETS)
|
|
1185
|
-
self.boot3_readiness = _dimension_score(self.findings, _BOOT3_MIGRATION_TARGETS)
|
|
1186
|
-
self.jdk_modernization = _dimension_score(self.findings, None)
|
|
1187
|
-
|
|
1188
|
-
# Hibernate is its own rewrite axis (orthogonal to jakarta/Boot3); it does
|
|
1189
|
-
# NOT sink the headline readiness_score, but is surfaced as a dimension and,
|
|
1190
|
-
# in a rewrite zone, as the headline_blocker so 62/100 is not read as "easy".
|
|
1191
|
-
_hibernate_applies = self.hibernate is not None and self.hibernate.detected
|
|
1192
|
-
if _hibernate_applies:
|
|
1193
|
-
self.hibernate_readiness = self.hibernate.readiness
|
|
1194
|
-
if self.hibernate.classification == "rewrite_zone":
|
|
1195
|
-
self.headline_blocker = "hibernate_rewrite"
|
|
1196
|
-
|
|
1197
|
-
# BUG #3: declare which dimensions apply. jakarta/boot3/jdk are always
|
|
1198
|
-
# applicable to a Java/Spring repo; hibernate applies only when a real
|
|
1199
|
-
# Hibernate dependency/import was found. An inapplicable dimension is N/A
|
|
1200
|
-
# (score=None) and is excluded from the aggregate — never scored as 0.
|
|
1300
|
+
hib_status = "unresolved"
|
|
1301
|
+
_hib_reason = ("N/A — Hibernate version unresolved (not declared and no Spring "
|
|
1302
|
+
"Boot BOM to infer from); not penalized on absent data")
|
|
1303
|
+
# Top-level scalar: the rewrite score only when applicable; else 100 (nothing
|
|
1304
|
+
# pending) — never a heuristic penalty on an inapplicable/unresolved axis.
|
|
1305
|
+
self.hibernate_readiness = _hib_score if _hibernate_applies else 100
|
|
1306
|
+
# Headline blocker only on a DIRECTLY-resolved Hibernate-5 rewrite zone.
|
|
1307
|
+
if _hibernate_applies and hib is not None and hib.classification == "rewrite_zone" \
|
|
1308
|
+
and hib.version_confidence == "high":
|
|
1309
|
+
self.headline_blocker = "hibernate_rewrite"
|
|
1310
|
+
|
|
1311
|
+
# BUG #3: declare which dimensions apply, then derive readiness_score as a
|
|
1312
|
+
# DOCUMENTED aggregate over the applicable MIGRATION dimensions only. jakarta
|
|
1313
|
+
# is always applicable; boot3 only with Spring; hibernate only for a real
|
|
1314
|
+
# Hibernate < 6. jdk_modernization is an orthogonal upkeep axis (SecurityManager,
|
|
1315
|
+
# reflection, java.time) — reported but EXCLUDED from the headline so JDK debt
|
|
1316
|
+
# cannot sink a framework-complete repo. N/A dimensions carry score=None and
|
|
1317
|
+
# never enter the aggregate.
|
|
1201
1318
|
self.applicable_dimensions = {
|
|
1202
1319
|
"jakarta": {"applicable": True, "score": self.jakarta_readiness,
|
|
1203
1320
|
"reason": "javax→jakarta namespace migration"},
|
|
1204
|
-
"boot3": {
|
|
1205
|
-
|
|
1321
|
+
"boot3": {
|
|
1322
|
+
"applicable": self.spring_present,
|
|
1323
|
+
"score": self.boot3_readiness if self.spring_present else None,
|
|
1324
|
+
"reason": ("Spring Boot 2→3 / Security 6 migration"
|
|
1325
|
+
if self.spring_present
|
|
1326
|
+
else "N/A — no Spring usage detected (non-Spring stack)"),
|
|
1327
|
+
},
|
|
1206
1328
|
"jdk_modernization": {"applicable": True, "score": self.jdk_modernization,
|
|
1207
|
-
"reason": "orthogonal JDK modernization debt"
|
|
1329
|
+
"reason": "orthogonal JDK modernization debt (excluded from "
|
|
1330
|
+
"the readiness_score aggregate)",
|
|
1331
|
+
"in_aggregate": False},
|
|
1208
1332
|
"hibernate": {
|
|
1209
1333
|
"applicable": _hibernate_applies,
|
|
1210
1334
|
"score": self.hibernate_readiness if _hibernate_applies else None,
|
|
1211
|
-
"reason":
|
|
1212
|
-
|
|
1213
|
-
else "N/A — no Hibernate dependency or import detected"),
|
|
1335
|
+
"reason": _hib_reason,
|
|
1336
|
+
"status": hib_status,
|
|
1214
1337
|
},
|
|
1215
1338
|
}
|
|
1216
1339
|
|
|
1340
|
+
# ── BUG #3: aggregate invariant ─────────────────────────────────────────
|
|
1341
|
+
# readiness_score == min(score of every applicable migration dimension).
|
|
1342
|
+
# MIN (not mean): a migration is only as ready as its weakest applicable
|
|
1343
|
+
# axis. _MIGRATION_DIMENSIONS lists exactly which dimensions feed it; the
|
|
1344
|
+
# consistency invariant is asserted in finalize and covered by a unit test.
|
|
1345
|
+
agg_inputs = {
|
|
1346
|
+
name: self.applicable_dimensions[name]["score"]
|
|
1347
|
+
for name in _MIGRATION_DIMENSIONS
|
|
1348
|
+
if self.applicable_dimensions[name]["applicable"]
|
|
1349
|
+
}
|
|
1350
|
+
self.readiness_score = min(agg_inputs.values()) if agg_inputs else 100
|
|
1351
|
+
self.readiness_aggregate = {
|
|
1352
|
+
"method": "min",
|
|
1353
|
+
"inputs": agg_inputs,
|
|
1354
|
+
"excluded": ["jdk_modernization"],
|
|
1355
|
+
"note": ("readiness_score = min over applicable migration dimensions "
|
|
1356
|
+
"(jakarta / boot3 / hibernate). jdk_modernization is an orthogonal "
|
|
1357
|
+
"upkeep axis and is intentionally excluded."),
|
|
1358
|
+
}
|
|
1359
|
+
# Internal consistency guard — the headline cannot diverge from the dimensions
|
|
1360
|
+
# it claims to summarize (catches a future scorer change that breaks the model).
|
|
1361
|
+
if agg_inputs:
|
|
1362
|
+
assert self.readiness_score == min(agg_inputs.values()), (
|
|
1363
|
+
"readiness_score must equal min(applicable migration dimensions); "
|
|
1364
|
+
f"got {self.readiness_score} vs inputs {agg_inputs}"
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
# BUG #5: effort over MAIN findings only — N/A axes (Hibernate-6 phantom,
|
|
1368
|
+
# test fixtures) no longer pad the estimate.
|
|
1217
1369
|
self.estimated_effort_days = round(
|
|
1218
1370
|
len(critical_files) * 0.5
|
|
1219
1371
|
+ len(high_files) * 0.25
|
|
@@ -1222,12 +1374,34 @@ class MigrationReport:
|
|
|
1222
1374
|
1,
|
|
1223
1375
|
)
|
|
1224
1376
|
|
|
1377
|
+
# BUG #6 / #8: hygiene + non-blocking buckets, surfaced separately.
|
|
1378
|
+
self.hygiene_findings = sum(
|
|
1379
|
+
1 for f in main_findings if f.migration_target in _BEST_PRACTICE_TARGETS
|
|
1380
|
+
)
|
|
1381
|
+
_nb_by_ctx: dict[str, int] = {}
|
|
1382
|
+
_nb_by_rule: dict[str, int] = {}
|
|
1383
|
+
for f in self.findings:
|
|
1384
|
+
if f.code_context == "main":
|
|
1385
|
+
continue
|
|
1386
|
+
_nb_by_ctx[f.code_context] = _nb_by_ctx.get(f.code_context, 0) + 1
|
|
1387
|
+
_nb_by_rule[f.rule_id] = _nb_by_rule.get(f.rule_id, 0) + 1
|
|
1388
|
+
self.non_blocking = {
|
|
1389
|
+
"count": sum(_nb_by_ctx.values()),
|
|
1390
|
+
"by_context": _nb_by_ctx,
|
|
1391
|
+
"by_rule": _nb_by_rule,
|
|
1392
|
+
"note": ("Findings in test/fixture harnesses or autogenerated sources. "
|
|
1393
|
+
"Excluded from blocking_count, readiness, and effort."),
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1225
1396
|
self.summary = {
|
|
1226
1397
|
"total_findings": len(self.findings),
|
|
1227
1398
|
"affected_files": len(affected_files),
|
|
1228
1399
|
"by_severity": by_severity,
|
|
1229
1400
|
"by_rule": by_rule,
|
|
1230
1401
|
"by_migration_target": by_target,
|
|
1402
|
+
"main_findings": len(main_findings),
|
|
1403
|
+
"non_blocking_findings": self.non_blocking["count"],
|
|
1404
|
+
"hygiene_findings": self.hygiene_findings,
|
|
1231
1405
|
}
|
|
1232
1406
|
return self
|
|
1233
1407
|
|
|
@@ -1243,14 +1417,20 @@ class MigrationReport:
|
|
|
1243
1417
|
"jdk_modernization": self.jdk_modernization,
|
|
1244
1418
|
"hibernate_readiness": self.hibernate_readiness,
|
|
1245
1419
|
"applicable_dimensions": self.applicable_dimensions,
|
|
1420
|
+
"readiness_aggregate": self.readiness_aggregate,
|
|
1246
1421
|
"readiness_note": (
|
|
1247
|
-
"readiness_score
|
|
1248
|
-
"(
|
|
1249
|
-
"
|
|
1422
|
+
"readiness_score = min over applicable MIGRATION dimensions "
|
|
1423
|
+
"(jakarta / boot3 / hibernate); see readiness_aggregate for the exact "
|
|
1424
|
+
"inputs. N/A dimensions are excluded (never counted as 0); "
|
|
1425
|
+
"jdk_modernization is orthogonal upkeep and is NOT in the aggregate. "
|
|
1426
|
+
"For decisions read the per-dimension breakdown + blocking_count."
|
|
1250
1427
|
),
|
|
1251
1428
|
"headline_blocker": self.headline_blocker,
|
|
1252
1429
|
"blocking_count": self.blocking_count,
|
|
1253
1430
|
"estimated_effort_days": self.estimated_effort_days,
|
|
1431
|
+
"hygiene_findings": self.hygiene_findings,
|
|
1432
|
+
"non_blocking": self.non_blocking,
|
|
1433
|
+
"spring_present": self.spring_present,
|
|
1254
1434
|
"spring_boot_2_detected": self.spring_boot_2_detected,
|
|
1255
1435
|
"spring_boot_version_detected": self.spring_boot_version_detected,
|
|
1256
1436
|
"summary": self.summary,
|
|
@@ -1270,19 +1450,31 @@ class MigrationReport:
|
|
|
1270
1450
|
_boot = f"Boot {self.spring_boot_version_detected or '3+'} detected"
|
|
1271
1451
|
else:
|
|
1272
1452
|
_boot = "unknown"
|
|
1453
|
+
|
|
1454
|
+
def _dim(name: str) -> str:
|
|
1455
|
+
d = self.applicable_dimensions.get(name, {})
|
|
1456
|
+
if not d.get("applicable", True):
|
|
1457
|
+
return f"{name}: N/A"
|
|
1458
|
+
return f"{name}: {d.get('score')}/100"
|
|
1459
|
+
|
|
1460
|
+
# Blocking parenthetical reflects MAIN (product) findings only — matching
|
|
1461
|
+
# blocking_count — so the text headline cannot contradict the JSON.
|
|
1462
|
+
main_crit = sum(1 for f in self.findings
|
|
1463
|
+
if f.code_context == "main" and f.severity == "critical")
|
|
1464
|
+
main_high = sum(1 for f in self.findings
|
|
1465
|
+
if f.code_context == "main" and f.severity == "high")
|
|
1466
|
+
nb = self.non_blocking.get("count", 0)
|
|
1273
1467
|
lines: list[str] = [
|
|
1274
1468
|
f"Migration Readiness: {self.readiness_score}/100",
|
|
1275
|
-
f" jakarta
|
|
1276
|
-
f"
|
|
1277
|
-
f"jdk-modernization: {self.jdk_modernization}/100 "
|
|
1278
|
-
f"hibernate: {self.hibernate_readiness}/100",
|
|
1469
|
+
f" {_dim('jakarta')} {_dim('boot3')} "
|
|
1470
|
+
f"{_dim('jdk_modernization')} {_dim('hibernate')}",
|
|
1279
1471
|
*([f" ⚠ Headline blocker: {self.headline_blocker} "
|
|
1280
1472
|
f"(readiness_score reflects jakarta/Boot3 only — Hibernate is a separate rewrite axis)"]
|
|
1281
1473
|
if self.headline_blocker else []),
|
|
1282
|
-
f"Spring Boot 2 detected: {_boot}",
|
|
1283
|
-
f"Blocking issues: {self.blocking_count} "
|
|
1284
|
-
f"(critical: {
|
|
1285
|
-
f"
|
|
1474
|
+
f"Spring present: {self.spring_present} Spring Boot 2 detected: {_boot}",
|
|
1475
|
+
f"Blocking issues (product code): {self.blocking_count} "
|
|
1476
|
+
f"(critical: {main_crit}, high: {main_high})"
|
|
1477
|
+
+ (f" [+{nb} in test/generated, non-blocking]" if nb else ""),
|
|
1286
1478
|
f"Affected files: {self.summary.get('affected_files', 0)}",
|
|
1287
1479
|
f"Estimated effort: {self.estimated_effort_days}d",
|
|
1288
1480
|
"",
|
|
@@ -1314,6 +1506,42 @@ class MigrationReport:
|
|
|
1314
1506
|
# Java source scanner
|
|
1315
1507
|
# ---------------------------------------------------------------------------
|
|
1316
1508
|
|
|
1509
|
+
# BUG #7: javax.annotation.Generated is the JSR-250 marker emitted into
|
|
1510
|
+
# AUTOGENERATED sources (SCIM, JAXB, MapStruct…). It maps mechanically to
|
|
1511
|
+
# jakarta.annotation.Generated and carries low migration value — it must not be
|
|
1512
|
+
# described with the generic "@PostConstruct/@PreDestroy/@Resource" blurb, nor
|
|
1513
|
+
# rated medium alongside real CDI lifecycle annotations.
|
|
1514
|
+
_MIG006_LIFECYCLE = ("PostConstruct", "PreDestroy", "Resource",
|
|
1515
|
+
"ManagedBean", "Priority", "Resources")
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
def _refine_mig006(matched_imports: list[str]) -> tuple[str, str]:
|
|
1519
|
+
"""Return (severity, explanation) tailored to the actual javax.annotation symbols.
|
|
1520
|
+
|
|
1521
|
+
Explanation names the concrete symbols detected, not a fixed list. When the
|
|
1522
|
+
only symbol is `Generated`, severity degrades to low (mechanical, generated
|
|
1523
|
+
code); otherwise the CDI lifecycle annotations keep medium severity.
|
|
1524
|
+
"""
|
|
1525
|
+
symbols = [imp.rsplit(".", 1)[-1] for imp in matched_imports]
|
|
1526
|
+
lifecycle = [s for s in symbols if s in _MIG006_LIFECYCLE]
|
|
1527
|
+
only_generated = bool(symbols) and all(s == "Generated" for s in symbols)
|
|
1528
|
+
sym_list = ", ".join(f"@{s}" for s in dict.fromkeys(symbols)) or "javax.annotation"
|
|
1529
|
+
if only_generated:
|
|
1530
|
+
return (
|
|
1531
|
+
"low",
|
|
1532
|
+
"javax.annotation.Generated (JSR-250) maps mechanically to "
|
|
1533
|
+
"jakarta.annotation.Generated. It is emitted into autogenerated sources "
|
|
1534
|
+
"and carries low migration value — re-run the generator on the Jakarta "
|
|
1535
|
+
"toolchain rather than hand-editing.",
|
|
1536
|
+
)
|
|
1537
|
+
affected = ", ".join(f"@{s}" for s in dict.fromkeys(lifecycle)) or sym_list
|
|
1538
|
+
return (
|
|
1539
|
+
"medium",
|
|
1540
|
+
f"jakarta.annotation replaces javax.annotation in Jakarta EE 9+. "
|
|
1541
|
+
f"Detected symbol(s): {sym_list}. {affected} are affected.",
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
|
|
1317
1545
|
def _scan_file(
|
|
1318
1546
|
source: str,
|
|
1319
1547
|
rel_path: str,
|
|
@@ -1360,16 +1588,21 @@ def _scan_file(
|
|
|
1360
1588
|
first_line = min(candidate_lines)
|
|
1361
1589
|
all_matches = matched_imports + code_snippets
|
|
1362
1590
|
|
|
1591
|
+
severity = rule.severity
|
|
1592
|
+
explanation = rule.explanation
|
|
1593
|
+
if rule.id == "MIG-006" and matched_imports:
|
|
1594
|
+
severity, explanation = _refine_mig006(matched_imports)
|
|
1595
|
+
|
|
1363
1596
|
findings.append(
|
|
1364
1597
|
MigrationFinding(
|
|
1365
1598
|
id=MigrationFinding.make_id(rule.id, rel_path),
|
|
1366
1599
|
rule_id=rule.id,
|
|
1367
|
-
severity=
|
|
1600
|
+
severity=severity,
|
|
1368
1601
|
title=rule.title,
|
|
1369
1602
|
source_file=rel_path,
|
|
1370
1603
|
first_line=first_line,
|
|
1371
1604
|
imports_found=all_matches,
|
|
1372
|
-
explanation=
|
|
1605
|
+
explanation=explanation,
|
|
1373
1606
|
fix_hint=rule.fix_hint,
|
|
1374
1607
|
migration_target=rule.migration_target,
|
|
1375
1608
|
openrewrite_recipe=rule.openrewrite_recipe,
|
|
@@ -1410,6 +1643,7 @@ def run_migrate_check(
|
|
|
1410
1643
|
limitations: list[str] = []
|
|
1411
1644
|
read_errors = 0
|
|
1412
1645
|
jakarta_import_count = 0
|
|
1646
|
+
spring_import_seen = False
|
|
1413
1647
|
|
|
1414
1648
|
# ── Java source scan ────────────────────────────────────────────────────
|
|
1415
1649
|
for rel_path in file_paths:
|
|
@@ -1422,6 +1656,8 @@ def run_migrate_check(
|
|
|
1422
1656
|
|
|
1423
1657
|
# Jakarta EE 9+ namespace adoption signal (vetoes a false Boot-2 verdict).
|
|
1424
1658
|
jakarta_import_count += len(_JAKARTA_IMPORT_RE.findall(source))
|
|
1659
|
+
if not spring_import_seen and _SPRING_IMPORT_RE.search(source):
|
|
1660
|
+
spring_import_seen = True
|
|
1425
1661
|
|
|
1426
1662
|
file_findings = _scan_file(source, rel_path, _ALL_RULES)
|
|
1427
1663
|
filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
@@ -1477,6 +1713,12 @@ def run_migrate_check(
|
|
|
1477
1713
|
limitations.extend(_STATIC_LIMITATIONS)
|
|
1478
1714
|
|
|
1479
1715
|
spring_boot_2, spring_boot_version = _detect_spring_boot(root, jakarta_import_count)
|
|
1716
|
+
spring_present = _detect_spring_present(root, spring_import_seen)
|
|
1717
|
+
|
|
1718
|
+
# BUG #8: classify each finding's code context (main / test / generated) so the
|
|
1719
|
+
# report can keep test fixtures and autogenerated sources out of blocking_count.
|
|
1720
|
+
for f in all_findings:
|
|
1721
|
+
f.code_context = _classify_code_context(f)
|
|
1480
1722
|
|
|
1481
1723
|
# Hibernate 5→6 stratification (independent of min_severity — it is its own
|
|
1482
1724
|
# risk model, not a severity-filtered finding stream).
|
|
@@ -1486,6 +1728,7 @@ def run_migrate_check(
|
|
|
1486
1728
|
report = MigrationReport(
|
|
1487
1729
|
spring_boot_2_detected=spring_boot_2,
|
|
1488
1730
|
spring_boot_version_detected=spring_boot_version,
|
|
1731
|
+
spring_present=spring_present,
|
|
1489
1732
|
findings=all_findings,
|
|
1490
1733
|
hibernate=hibernate_strat,
|
|
1491
1734
|
limitations=limitations,
|
sourcecode/path_filters.py
CHANGED
|
@@ -12,6 +12,16 @@ _TEST_SEGMENTS = frozenset({
|
|
|
12
12
|
"integrationtest", "integrationtests",
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
+
# Whole-module directories that are test harness / fixtures even though their
|
|
16
|
+
# code lives under src/main (e.g. a Maven module that ships a test framework).
|
|
17
|
+
# A finding under one of these modules is test infrastructure, not the product.
|
|
18
|
+
_TEST_MODULE_SEGMENTS = frozenset({
|
|
19
|
+
"testsuite", "test-framework", "testframework",
|
|
20
|
+
"integration-arquillian", "arquillian",
|
|
21
|
+
"test-utils", "test-util", "testutils",
|
|
22
|
+
"test-support", "testsupport",
|
|
23
|
+
})
|
|
24
|
+
|
|
15
25
|
_VENDOR_SEGMENTS = frozenset({
|
|
16
26
|
"vendor", "vendors",
|
|
17
27
|
"third_party", "thirdparty",
|
|
@@ -86,6 +96,29 @@ def is_test_path(path: str) -> bool:
|
|
|
86
96
|
return False
|
|
87
97
|
|
|
88
98
|
|
|
99
|
+
def is_test_or_fixture_path(path: str) -> bool:
|
|
100
|
+
"""Return True when *path* is test code OR test-harness/fixture infrastructure.
|
|
101
|
+
|
|
102
|
+
Broader than is_test_path: also catches whole modules that are test frameworks
|
|
103
|
+
or integration-test harnesses (testsuite/, test-framework/, integration-arquillian/,
|
|
104
|
+
*-test*, *-it modules) even when their sources sit under src/main. Migration
|
|
105
|
+
tooling uses this to keep test fixtures (deprecated web.xml deployment
|
|
106
|
+
descriptors, test-only sun.* HTTP servers) out of the product's blocking count.
|
|
107
|
+
"""
|
|
108
|
+
if is_test_path(path):
|
|
109
|
+
return True
|
|
110
|
+
norm = path.replace("\\", "/").lower()
|
|
111
|
+
parts = norm.split("/")
|
|
112
|
+
for part in parts[:-1]: # skip the filename
|
|
113
|
+
bare = part.rstrip("/")
|
|
114
|
+
if bare in _TEST_MODULE_SEGMENTS:
|
|
115
|
+
return True
|
|
116
|
+
# module dirs like "adapter-test", "foo-tests", "bar-itests"
|
|
117
|
+
if bare.endswith(("-test", "-tests", "-it", "-itest", "-itests")):
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
89
122
|
def is_vendor_path(path: str) -> bool:
|
|
90
123
|
"""Return True when *path* is inside a vendored / third-party directory.
|
|
91
124
|
|
sourcecode/serializer.py
CHANGED
|
@@ -291,17 +291,65 @@ _JAKARTA_RENAMED_NAMESPACES: tuple[str, ...] = (
|
|
|
291
291
|
)
|
|
292
292
|
|
|
293
293
|
|
|
294
|
+
# Permanent JDK / JSR javax.* namespaces. These are part of the Java SE platform
|
|
295
|
+
# (or standalone JCP specs) and were NEVER renamed to jakarta.* — Spring Boot 3/4
|
|
296
|
+
# still uses them under javax.*. They must be excluded from EVERY javax→jakarta
|
|
297
|
+
# detection rule (dependency coordinates AND import scanning). This is the single
|
|
298
|
+
# source of truth for the allowlist — do NOT re-implement prefix checks elsewhere.
|
|
299
|
+
# Counterpart in migrate_check._JAKARTA_NO_MIGRATE_PREFIXES (import side); keep both
|
|
300
|
+
# in sync. NOTE: `javax.annotation.processing` is permanent (JSR-269, compiler API)
|
|
301
|
+
# but bare `javax.annotation` (JSR-250 EE subset) DID move — handled below.
|
|
302
|
+
_JAVAX_PERMANENT_NAMESPACES: tuple[str, ...] = (
|
|
303
|
+
"javax.cache", # JSR-107 (JCache) — never renamed
|
|
304
|
+
"javax.sql", # JDBC standard extension (Java SE)
|
|
305
|
+
"javax.xml", # JAXP (parsers/transform/xpath/stream/namespace…)
|
|
306
|
+
"javax.naming", # JNDI (Java SE)
|
|
307
|
+
"javax.management", # JMX (Java SE)
|
|
308
|
+
"javax.crypto", # JCE (Java SE)
|
|
309
|
+
"javax.net", # incl. javax.net.ssl (Java SE)
|
|
310
|
+
"javax.security.auth", # JAAS (Java SE)
|
|
311
|
+
"javax.security.cert", # Java SE
|
|
312
|
+
"javax.security.sasl", # Java SE
|
|
313
|
+
"javax.annotation.processing", # JSR-269 compiler API (NOT the JSR-250 subset)
|
|
314
|
+
"javax.tools", # Java SE compiler API
|
|
315
|
+
"javax.lang.model", # Java SE annotation-processing model
|
|
316
|
+
"javax.sound", # Java SE
|
|
317
|
+
"javax.imageio", # Java SE
|
|
318
|
+
"javax.print", # Java SE
|
|
319
|
+
"javax.accessibility", # Java SE
|
|
320
|
+
"javax.swing", # Java SE
|
|
321
|
+
"javax.smartcardio", # Java SE
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _longest_namespace_match(nl: str, namespaces: "tuple[str, ...]") -> int:
|
|
326
|
+
"""Length of the longest namespace in *namespaces* that is a prefix of *nl*.
|
|
327
|
+
|
|
328
|
+
Matches an exact namespace or a dotted sub-package (javax.xml → javax.xml.bind);
|
|
329
|
+
returns 0 when none match.
|
|
330
|
+
"""
|
|
331
|
+
best = 0
|
|
332
|
+
for ns in namespaces:
|
|
333
|
+
if (nl == ns or nl.startswith(ns + ".")) and len(ns) > best:
|
|
334
|
+
best = len(ns)
|
|
335
|
+
return best
|
|
336
|
+
|
|
337
|
+
|
|
294
338
|
def _is_jakarta_renamed_namespace(nl: str) -> bool:
|
|
295
339
|
"""True only for javax.* namespaces actually renamed to jakarta.* in Jakarta EE 9.
|
|
296
340
|
|
|
297
|
-
|
|
341
|
+
Decided by LONGEST-prefix match across the renamed list and the permanent
|
|
342
|
+
JDK/JSR allowlist — the more specific namespace wins. This keeps both nuanced
|
|
343
|
+
cases correct simultaneously:
|
|
344
|
+
- javax.xml.bind (renamed, len 14) beats javax.xml (permanent, len 9) → flag.
|
|
345
|
+
- javax.annotation.processing (permanent, len 27) beats javax.annotation
|
|
346
|
+
(renamed, len 16) → no flag.
|
|
298
347
|
JDK/JSR namespaces that keep javax.* forever (javax.cache, javax.sql, javax.xml
|
|
299
|
-
JAXP, javax.naming, …)
|
|
348
|
+
JAXP, javax.naming, …) never flag. A bare javax.* with no match also never flags.
|
|
300
349
|
"""
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return False
|
|
350
|
+
renamed = _longest_namespace_match(nl, _JAKARTA_RENAMED_NAMESPACES)
|
|
351
|
+
permanent = _longest_namespace_match(nl, _JAVAX_PERMANENT_NAMESPACES)
|
|
352
|
+
return renamed > 0 and renamed >= permanent
|
|
305
353
|
|
|
306
354
|
|
|
307
355
|
def _dep_risk_flags(name: str, version: "Optional[str]") -> list[str]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.66.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -411,7 +411,7 @@ Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the ki
|
|
|
411
411
|
|------|--------|
|
|
412
412
|
| `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
|
|
413
413
|
| `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
|
|
414
|
-
| `--integrations` | Outbound integrations (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `
|
|
414
|
+
| `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`) + `coverage_note`: a count of `0` means "no client construct found", **not** "no integrations" (runtime/DI-wired clients are not statically visible). |
|
|
415
415
|
| `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). `components.module_roots` rolls leaf source dirs up to architectural module roots and classifies each `layered` (DDD: ≥2 of `domain`/`application`/`infrastructure`) vs `flat` (legacy/flat package), with a verifiable `module_count` — so a consumer enumerates real modules instead of inferring boundaries from leaf directories. |
|
|
416
416
|
|
|
417
417
|
The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
|
|
@@ -610,7 +610,7 @@ Detects migration blockers across Java source files, Spring XML config files, an
|
|
|
610
610
|
| `MIG-002` | high | `javax.servlet` import — Servlet API changed |
|
|
611
611
|
| `MIG-003` | high | `javax.validation` import — Bean Validation changed |
|
|
612
612
|
| `MIG-004` | high | `javax.transaction` import — TX API changed |
|
|
613
|
-
| `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
|
|
613
|
+
| `MIG-006` | medium¹ | `javax.annotation` import — CDI annotations changed (¹`javax.annotation.Generated` in autogenerated code → **low**, bucketed `generated`; explanation names the actual symbol) |
|
|
614
614
|
| `MIG-007` | medium | `javax.inject` import — DI annotations changed |
|
|
615
615
|
| `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
|
|
616
616
|
| `MIG-009` | medium | `javax.jms` import — JMS API changed |
|
|
@@ -722,9 +722,51 @@ reported as **N/A** (`score: null`), never as `0`. The top-level
|
|
|
722
722
|
states that `readiness_score` is a derived aggregate over applicable dimensions
|
|
723
723
|
only — for decisions, read the per-dimension breakdown + `blocking_count`.
|
|
724
724
|
|
|
725
|
+
#### Reliability guarantees (no version, no verdict)
|
|
726
|
+
|
|
727
|
+
A migration verdict shown to a decision-maker must never contradict its own
|
|
728
|
+
evidence. `migrate-check` enforces:
|
|
729
|
+
|
|
730
|
+
- **Version-gated framework axes.** The Hibernate 5→6 axis applies only when the
|
|
731
|
+
effective Hibernate version (resolved from `pom.xml`/Gradle, following Maven
|
|
732
|
+
`${properties}` and BOM-managed `<hibernate*.version>`) is `< 6`. A resolved
|
|
733
|
+
Hibernate **6+** repo is reported `migration_applicable: false` (N/A) — no
|
|
734
|
+
`hibernate_rewrite` headline. An **unresolved** version degrades to a
|
|
735
|
+
low-confidence hypothesis with no headline blocker. See
|
|
736
|
+
`hibernate.effective_version` / `version_major` / `version_confidence`.
|
|
737
|
+
- **Boot3 auto-disables without Spring.** `boot3.applicable` tracks real Spring
|
|
738
|
+
usage; Quarkus / Micronaut / Helidon / Jakarta-pure repos report `boot3` as N/A
|
|
739
|
+
(`spring_present: false`), not a contradictory `applicable: true`.
|
|
740
|
+
- **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
|
|
741
|
+
(`javax.cache`, `javax.sql`, `javax.xml` JAXP, `javax.crypto`, `javax.naming`,
|
|
742
|
+
`javax.management`, `javax.security.auth`, `javax.annotation.processing`, …) are
|
|
743
|
+
allowlisted across every jakarta scorer (single source of truth:
|
|
744
|
+
`serializer._JAVAX_PERMANENT_NAMESPACES` + `migrate_check._JAKARTA_NO_MIGRATE_PREFIXES`).
|
|
745
|
+
The `javax→jakarta` dependency flag decides via **longest-prefix match**, so
|
|
746
|
+
`javax.xml.bind` (JAXB — moved) flags while `javax.xml` (JAXP) does not, and
|
|
747
|
+
`javax.annotation` (JSR-250 — moved) flags while `javax.annotation.processing`
|
|
748
|
+
(JSR-269) does not. The allowlist never silences a real migration.
|
|
749
|
+
- **`readiness_score` is a traceable aggregate.** It is `min` over the applicable
|
|
750
|
+
**migration** dimensions (`jakarta` / `boot3` / `hibernate`); `jdk_modernization`
|
|
751
|
+
is orthogonal upkeep and is excluded. The exact inputs are in `readiness_aggregate{}`
|
|
752
|
+
and an invariant (`readiness_score == min(applicable migration dims)`) is asserted in
|
|
753
|
+
code. When the Hibernate version is undeclared it is inferred from the Spring Boot
|
|
754
|
+
BOM (Boot ≥3 → Hibernate ≥6 → N/A; Boot 2 → Hibernate 5 → applicable; no BOM →
|
|
755
|
+
`status: unresolved`, never a heuristic score).
|
|
756
|
+
- **Three buckets: blocker ≠ hygiene ≠ test.** Only product (`main`) code counts
|
|
757
|
+
toward `blocking_count`, readiness, and effort. Test harnesses
|
|
758
|
+
(`testsuite/`, `test-framework/`, `integration-arquillian/`, `*-test*`,
|
|
759
|
+
`src/test/`) and autogenerated sources are tagged `code_context` and surfaced in
|
|
760
|
+
`non_blocking{}`. Best-practice hygiene (`java.util.Date`) is reported as
|
|
761
|
+
`hygiene_findings` and never sinks a dimension. Each finding carries
|
|
762
|
+
`code_context: main | test | generated`.
|
|
763
|
+
|
|
725
764
|
```bash
|
|
726
765
|
# inspect only the Hibernate rewrite targets
|
|
727
766
|
sourcecode migrate-check . --format json | jq '.hibernate.rewrite_targets[]'
|
|
767
|
+
|
|
768
|
+
# product blockers only (excludes test/fixtures + hygiene)
|
|
769
|
+
sourcecode migrate-check . --format json | jq '.blocking_count, .non_blocking, .hygiene_findings'
|
|
728
770
|
```
|
|
729
771
|
|
|
730
772
|
### `rename-class` — Java class rename
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=_rKEwisXr1IeFjMbYXGSfdRcelQmOHaAB0XR84P9tsk,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=liCwQmLgb5vplohy8arjYxs_HOIv5C9MjLh_gY6bc5Q,44115
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -28,15 +28,15 @@ sourcecode/format_contract.py,sha256=1cTNqwP8geA2hbQoBHUPgX3_vSh3l8guJT_jmgEnFF8
|
|
|
28
28
|
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
|
-
sourcecode/hibernate_strat.py,sha256=
|
|
32
|
-
sourcecode/integration_detector.py,sha256=
|
|
31
|
+
sourcecode/hibernate_strat.py,sha256=ZyI0sikxh7IemU8uqn1Z73z-tHeFeCJuYhJnyQL8pzU,57765
|
|
32
|
+
sourcecode/integration_detector.py,sha256=mQulsXN1P-4V_3ueh3xy_N9x3Aqlvm7GQ-3YGqlBEYM,8223
|
|
33
33
|
sourcecode/license.py,sha256=wckiLuiwaE35KMCStUf1gYzleJuFe6qsSyxUQJvit3s,23500
|
|
34
34
|
sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
|
|
35
35
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
36
|
-
sourcecode/migrate_check.py,sha256=
|
|
36
|
+
sourcecode/migrate_check.py,sha256=daZLA7KaeoWoUPSMN8cYqHYD6HBflzc-z_N4bCGVPak,82993
|
|
37
37
|
sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
|
|
38
38
|
sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
|
|
39
|
-
sourcecode/path_filters.py,sha256=
|
|
39
|
+
sourcecode/path_filters.py,sha256=DMea0KIlmj2pzTkuy-3YYFF4ktP0q5zP9mH4v4uXh84,5237
|
|
40
40
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
41
41
|
sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
|
|
42
42
|
sourcecode/prepare_context.py,sha256=PsRaKeABMh964niCS578WRgcccW9bajnm2yHS9zcKuM,222655
|
|
@@ -53,7 +53,7 @@ sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
|
53
53
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
54
54
|
sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs,3503
|
|
55
55
|
sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIxkw,89176
|
|
56
|
-
sourcecode/serializer.py,sha256=
|
|
56
|
+
sourcecode/serializer.py,sha256=MSxYZ-_UYDPKMvg-hVk-MnKN-TfrmxXL202siCssa9U,129110
|
|
57
57
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
58
58
|
sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
|
|
59
59
|
sourcecode/spring_impact.py,sha256=qLwLfItX_o9LU-k_qjhD2hFpTX3PpEQ85TsYTAArzvg,56016
|
|
@@ -103,8 +103,8 @@ sourcecode/telemetry/consent.py,sha256=H5z2Wu63pZqbaKucRPoJQJ0zCo4cke9ZlBrJC-MaW
|
|
|
103
103
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
104
104
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
105
105
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
106
|
-
sourcecode-1.
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
109
|
-
sourcecode-1.
|
|
110
|
-
sourcecode-1.
|
|
106
|
+
sourcecode-1.66.0.dist-info/METADATA,sha256=PVY4maYclqHt2AKj_bcGmDzF7xmKhD0i7k2MrQepPKo,47341
|
|
107
|
+
sourcecode-1.66.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
108
|
+
sourcecode-1.66.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
109
|
+
sourcecode-1.66.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
110
|
+
sourcecode-1.66.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|