sourcecode 1.41.0__py3-none-any.whl → 1.42.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/repository_ir.py +75 -0
- sourcecode/spring_impact.py +66 -0
- {sourcecode-1.41.0.dist-info → sourcecode-1.42.0.dist-info}/METADATA +1 -1
- {sourcecode-1.41.0.dist-info → sourcecode-1.42.0.dist-info}/RECORD +8 -8
- {sourcecode-1.41.0.dist-info → sourcecode-1.42.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.41.0.dist-info → sourcecode-1.42.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.41.0.dist-info → sourcecode-1.42.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/repository_ir.py
CHANGED
|
@@ -134,6 +134,10 @@ _CLASS_DECL_RE = re.compile(
|
|
|
134
134
|
_METHOD_DECL_RE = re.compile(
|
|
135
135
|
r'^(?P<modifiers>(?:(?:public|private|protected|static|final|synchronized'
|
|
136
136
|
r'|abstract|default|native|strictfp|override)\s+)*)'
|
|
137
|
+
# Inline (modifier-position) annotations, e.g. `public @ResponseBody Vets foo()`
|
|
138
|
+
# or `@Valid`, `@Nonnull`. Without this the whole declaration fails to match and
|
|
139
|
+
# the method (its endpoint + return-type edge) is silently dropped (CH-003/Fase 22).
|
|
140
|
+
r'(?P<inline_anns>(?:@[\w.]+(?:\s*\([^)]*\))?\s+)*)'
|
|
137
141
|
r'(?:<[\w,\s?]+>\s+)?'
|
|
138
142
|
r'(?P<return_type>(?:void|boolean|byte|char|short|int|long|float|double|String|[\w.<>\[\]?,]+)\s+)'
|
|
139
143
|
r'(?P<name>[a-z_]\w*)\s*\(',
|
|
@@ -830,6 +834,11 @@ def _extract_symbols(
|
|
|
830
834
|
if mname not in _JAVA_KEYWORDS:
|
|
831
835
|
fqn = f"{class_fqn}#{mname}"
|
|
832
836
|
modifiers = _parse_modifier_str(mth_m.group("modifiers") or "")
|
|
837
|
+
# Fold modifier-position inline annotations into pending_anns so
|
|
838
|
+
# symbol_kind / endpoint detection see them (e.g. @ResponseBody).
|
|
839
|
+
for _inl in re.findall(r'@[\w.]+', mth_m.group("inline_anns") or ""):
|
|
840
|
+
if _inl not in pending_anns:
|
|
841
|
+
pending_anns.append(_inl)
|
|
833
842
|
used = _resolve_types_from_text(stripped, import_map)
|
|
834
843
|
conf = "high" if ("public" in modifiers or pending_anns) else "medium"
|
|
835
844
|
|
|
@@ -1330,6 +1339,32 @@ def _build_relations(
|
|
|
1330
1339
|
evidence={"type": "annotation", "value": ann},
|
|
1331
1340
|
))
|
|
1332
1341
|
|
|
1342
|
+
# ── Type-usage: return-type edges (CH-003 / Fase 22) ──────────────────────
|
|
1343
|
+
# A method's declared return type is a real dependency edge the call/DI graph
|
|
1344
|
+
# misses: for a value/DTO/response type returned (esp. @ResponseBody) by a
|
|
1345
|
+
# controller handler, this is the ONLY link from the type back to its endpoint.
|
|
1346
|
+
# method → returnTypeFQN, type="returns". Resolution reuses _resolve_dep_type,
|
|
1347
|
+
# so only in-repo / imported types produce edges (primitives & void are skipped).
|
|
1348
|
+
_PRIMITIVE_RETURNS: frozenset[str] = frozenset({
|
|
1349
|
+
"void", "boolean", "byte", "char", "short", "int", "long", "float",
|
|
1350
|
+
"double", "String", "Object",
|
|
1351
|
+
})
|
|
1352
|
+
for sym in symbols:
|
|
1353
|
+
if sym.type != "method" or not sym.return_type:
|
|
1354
|
+
continue
|
|
1355
|
+
_ret_base = re.sub(r'<.*', '', sym.return_type).replace("[]", "").strip()
|
|
1356
|
+
if not _ret_base or _ret_base in _PRIMITIVE_RETURNS or not _ret_base[0].isupper():
|
|
1357
|
+
continue
|
|
1358
|
+
_ret_fqn = _resolve_dep_type(_ret_base)
|
|
1359
|
+
if _ret_fqn and _ret_fqn != _enclosing_class(sym.symbol):
|
|
1360
|
+
edges.append(RelationEdge(
|
|
1361
|
+
from_symbol=sym.symbol,
|
|
1362
|
+
to_symbol=_ret_fqn,
|
|
1363
|
+
type="returns",
|
|
1364
|
+
confidence="high",
|
|
1365
|
+
evidence={"type": "return_type", "value": sym.return_type},
|
|
1366
|
+
))
|
|
1367
|
+
|
|
1333
1368
|
_class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
|
|
1334
1369
|
|
|
1335
1370
|
# Strip comments before event scanning to prevent Javadoc examples from
|
|
@@ -1425,6 +1460,46 @@ def _build_relations(
|
|
|
1425
1460
|
evidence={"type": "signature", "value": f"{ev_label}<{event_simple}>"},
|
|
1426
1461
|
))
|
|
1427
1462
|
|
|
1463
|
+
# ── Type-usage: instantiation edges (CH-003 / Fase 22) ────────────────────
|
|
1464
|
+
# `new T(...)` couples the instantiating class to T — a type-usage edge the
|
|
1465
|
+
# call/DI graph misses. Attributed at class level (one primary type per file,
|
|
1466
|
+
# mirroring the publishes_event scan). Controllers are EXCLUDED: their value-type
|
|
1467
|
+
# coupling is already covered precisely by `returns` edges, and a class-level
|
|
1468
|
+
# edge from a controller would broaden a DTO's impact to every route on that
|
|
1469
|
+
# controller (false breadth). Method-level attribution is a future refinement.
|
|
1470
|
+
# Controller-like = class-level @RestController/@Controller OR a class that owns
|
|
1471
|
+
# any endpoint-handler method (mappings are often only method-level).
|
|
1472
|
+
_classes_with_endpoints = {
|
|
1473
|
+
_enclosing_class(s.symbol) for s in symbols if s.symbol_kind == "endpoint"
|
|
1474
|
+
}
|
|
1475
|
+
_instantiating_controllers = {
|
|
1476
|
+
s.symbol for s in symbols
|
|
1477
|
+
if s.type in ("class", "interface")
|
|
1478
|
+
and (
|
|
1479
|
+
any(a in ("@RestController", "@Controller") for a in s.annotations)
|
|
1480
|
+
or s.symbol in _classes_with_endpoints
|
|
1481
|
+
)
|
|
1482
|
+
}
|
|
1483
|
+
_non_controller_classes = [
|
|
1484
|
+
s for s in _class_syms if s.symbol not in _instantiating_controllers
|
|
1485
|
+
]
|
|
1486
|
+
if _non_controller_classes:
|
|
1487
|
+
_inst_targets: set[str] = set()
|
|
1488
|
+
for _m in re.finditer(r'\bnew\s+([A-Z]\w*)\s*[(<]', _source_no_comments):
|
|
1489
|
+
_t_fqn = _resolve_dep_type(_m.group(1))
|
|
1490
|
+
if _t_fqn:
|
|
1491
|
+
_inst_targets.add(_t_fqn)
|
|
1492
|
+
for cls_sym in _non_controller_classes:
|
|
1493
|
+
for _tgt in sorted(_inst_targets):
|
|
1494
|
+
if _tgt != cls_sym.symbol:
|
|
1495
|
+
edges.append(RelationEdge(
|
|
1496
|
+
from_symbol=cls_sym.symbol,
|
|
1497
|
+
to_symbol=_tgt,
|
|
1498
|
+
type="instantiates",
|
|
1499
|
+
confidence="medium",
|
|
1500
|
+
evidence={"type": "method_call", "value": f"new {_tgt.split('.')[-1]}(...)"},
|
|
1501
|
+
))
|
|
1502
|
+
|
|
1428
1503
|
seen: set[tuple[str, str, str]] = set()
|
|
1429
1504
|
unique: list[RelationEdge] = []
|
|
1430
1505
|
for e in edges:
|
sourcecode/spring_impact.py
CHANGED
|
@@ -129,6 +129,50 @@ class ImpactChainResult:
|
|
|
129
129
|
return d
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# CH-003 — value/DTO type blind-spot detection
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# The impact graph models call + DI/injection edges but not *type-usage* edges
|
|
136
|
+
# (constructor instantiation `new T()`, field/local-variable type, and method
|
|
137
|
+
# return type incl. @ResponseBody). For a service/repository the call+DI edges
|
|
138
|
+
# cover the real blast radius; for a value/DTO/response type they cover nothing,
|
|
139
|
+
# so its impact is invisible — and an all-zero result reported at confidence=high
|
|
140
|
+
# reads as "globally dead" (a dangerous false negative). Until type-usage edges
|
|
141
|
+
# are modelled (Fase 22 / CH-002), positively identify plain value types and
|
|
142
|
+
# downgrade confidence + warn instead of asserting an empty high-confidence result.
|
|
143
|
+
_STEREOTYPE_ANNOTATIONS = frozenset({
|
|
144
|
+
"@Service", "@Repository", "@Controller", "@RestController",
|
|
145
|
+
"@Component", "@Configuration", "@ControllerAdvice",
|
|
146
|
+
"@RestControllerAdvice", "@Bean",
|
|
147
|
+
})
|
|
148
|
+
_VALUE_TYPE_KINDS = frozenset({"class", "enum", "record"})
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _is_unmodeled_value_type(cir, class_fqn: str, model) -> bool:
|
|
152
|
+
"""True iff class_fqn is positively a plain value/DTO type whose blast radius
|
|
153
|
+
flows only through type-usage edges the impact graph does not model.
|
|
154
|
+
|
|
155
|
+
Conservative: returns False whenever the type cannot be positively confirmed
|
|
156
|
+
(node metadata absent, stereotype annotation present, recognized Spring role,
|
|
157
|
+
or controller) so spine symbols and incomplete-IR cases keep legacy behaviour.
|
|
158
|
+
"""
|
|
159
|
+
graph = (getattr(cir, "_raw_ir", None) or {}).get("graph") or {}
|
|
160
|
+
node = next((n for n in (graph.get("nodes") or []) if n.get("fqn") == class_fqn), None)
|
|
161
|
+
if node is None:
|
|
162
|
+
return False # cannot confirm — stay conservative (preserves IC-V3)
|
|
163
|
+
if (node.get("symbol_kind") or node.get("type")) not in _VALUE_TYPE_KINDS:
|
|
164
|
+
return False # interface / annotation / bean-method etc.
|
|
165
|
+
anns = node.get("annotations") or []
|
|
166
|
+
if any(a.split("(", 1)[0] in _STEREOTYPE_ANNOTATIONS for a in anns):
|
|
167
|
+
return False # Spring stereotype bean — spine participant
|
|
168
|
+
if (node.get("role") or "other") != "other":
|
|
169
|
+
return False # recognized Spring role (repository/service/controller/mapper)
|
|
170
|
+
controllers = getattr(getattr(model, "endpoint_index", None), "controller_fqns", frozenset())
|
|
171
|
+
if class_fqn in controllers:
|
|
172
|
+
return False
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
|
|
132
176
|
# ---------------------------------------------------------------------------
|
|
133
177
|
# Symbol resolution
|
|
134
178
|
# ---------------------------------------------------------------------------
|
|
@@ -706,9 +750,31 @@ class ImpactOrchestrator:
|
|
|
706
750
|
impact_findings_raw,
|
|
707
751
|
)
|
|
708
752
|
|
|
753
|
+
# CH-003: empty blast radius on a positively-identified value/DTO type is a
|
|
754
|
+
# type-usage blind spot, not proof of dead code — warn + drop confidence.
|
|
755
|
+
empty_blast = (
|
|
756
|
+
not direct_callers and not indirect_callers
|
|
757
|
+
and not endpoints_affected and not subtype_classes_added
|
|
758
|
+
)
|
|
759
|
+
value_type_blind_spot = (
|
|
760
|
+
empty_blast
|
|
761
|
+
and "#" not in resolved_symbol
|
|
762
|
+
and resolution != "not_found"
|
|
763
|
+
and _is_unmodeled_value_type(cir, resolved_symbol, model)
|
|
764
|
+
)
|
|
765
|
+
if value_type_blind_spot:
|
|
766
|
+
warnings.append(
|
|
767
|
+
"Type-usage edges not modeled (CH-003): this type's blast radius flows "
|
|
768
|
+
"through instantiation (new T()), field/local types, and method return "
|
|
769
|
+
"types (incl. @ResponseBody) — edges impact-chain does not yet track. "
|
|
770
|
+
"An empty result is NOT proof the type is unused."
|
|
771
|
+
)
|
|
772
|
+
|
|
709
773
|
confidence: str
|
|
710
774
|
if resolution == "not_found":
|
|
711
775
|
confidence = "low"
|
|
776
|
+
elif value_type_blind_spot:
|
|
777
|
+
confidence = "low"
|
|
712
778
|
elif resolution == "partial" or warnings:
|
|
713
779
|
confidence = "medium"
|
|
714
780
|
else:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=qXpArucbDluvdwLNZfPMDizlqCc5pgoRka_QbdanHCg,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
|
|
@@ -44,7 +44,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
|
44
44
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
45
45
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
46
46
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
47
|
-
sourcecode/repository_ir.py,sha256
|
|
47
|
+
sourcecode/repository_ir.py,sha256=iSxk1Z5QZehHOG05naDxtT1_vhyyyPHlqzNDoK0HXXg,206573
|
|
48
48
|
sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
|
|
49
49
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
50
50
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
@@ -54,7 +54,7 @@ sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIx
|
|
|
54
54
|
sourcecode/serializer.py,sha256=TGzftrSKitZrtl6Hh-R05s4KdTOxwTmph_lGDbo2Wzg,125015
|
|
55
55
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
56
56
|
sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
|
|
57
|
-
sourcecode/spring_impact.py,sha256=
|
|
57
|
+
sourcecode/spring_impact.py,sha256=rUKSiCfXh9NpC9a97KvjCu6Kn8bYezTnMDY3F7sgtCI,38737
|
|
58
58
|
sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
|
|
59
59
|
sourcecode/spring_security_audit.py,sha256=XtPJ1SXlZJ8k6VYmaWuAp7Bbir4UmreAL7doIGQ5I7o,20595
|
|
60
60
|
sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM,13307
|
|
@@ -101,8 +101,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
101
101
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
102
102
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
103
103
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
104
|
-
sourcecode-1.
|
|
105
|
-
sourcecode-1.
|
|
106
|
-
sourcecode-1.
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
104
|
+
sourcecode-1.42.0.dist-info/METADATA,sha256=-O4gIf7ctCGPrfXwVg1AeRL0b8K-w9ShFnpbLl4Nyj8,32359
|
|
105
|
+
sourcecode-1.42.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
106
|
+
sourcecode-1.42.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
107
|
+
sourcecode-1.42.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
108
|
+
sourcecode-1.42.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|