sourcecode 1.35.23__py3-none-any.whl → 1.35.24__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.35.23"
3
+ __version__ = "1.35.24"
@@ -1,7 +1,8 @@
1
- """migrate_check.py — Spring Boot 2→3 (javax→jakarta) migration readiness checker.
1
+ """migrate_check.py — Java 8/Spring Boot 2 migration readiness checker.
2
2
 
3
- Scans Java source files for import namespaces and class patterns that must be
4
- updated when migrating from Spring Boot 2.x (javax.*) to Spring Boot 3.x (jakarta.*).
3
+ Scans Java source files for patterns that must be addressed when migrating:
4
+ - Spring Boot 2 → 3 (javax jakarta, Spring Security 6)
5
+ - Java 8 → 17 / 21 (SecurityManager, Nashorn, Unsafe, reflection, etc.)
5
6
 
6
7
  Entry point: run_migrate_check(file_paths, root) → MigrationReport
7
8
  """
@@ -26,11 +27,18 @@ class _Rule:
26
27
  title: str
27
28
  explanation: str
28
29
  fix_hint: str
30
+ migration_target: str = "spring_boot_3" # jakarta | spring_security_6 | java_11 | java_15 | java_17 | java_9_plus | java_18_plus
31
+ openrewrite_recipe: Optional[str] = None # Official OpenRewrite recipe ID, if one exists
29
32
  import_pattern: Optional[re.Pattern] = None # matches the import statement
30
33
  extends_pattern: Optional[re.Pattern] = None # matches an extends clause
34
+ code_pattern: Optional[re.Pattern] = None # matches arbitrary code anywhere in the file
31
35
 
32
36
 
33
- _IMPORT_RULES: list[_Rule] = [
37
+ # ---------------------------------------------------------------------------
38
+ # Jakarta namespace rules (Spring Boot 2 → 3)
39
+ # ---------------------------------------------------------------------------
40
+
41
+ _JAKARTA_RULES: list[_Rule] = [
34
42
  _Rule(
35
43
  id="MIG-001",
36
44
  severity="critical",
@@ -40,6 +48,8 @@ _IMPORT_RULES: list[_Rule] = [
40
48
  "namespace. Files importing javax.persistence will not compile after migration."
41
49
  ),
42
50
  fix_hint="Replace 'javax.persistence' with 'jakarta.persistence' across all affected files.",
51
+ migration_target="jakarta",
52
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence",
43
53
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.persistence[^;]+);", re.MULTILINE),
44
54
  ),
45
55
  _Rule(
@@ -51,6 +61,8 @@ _IMPORT_RULES: list[_Rule] = [
51
61
  "HttpServletResponse referencing javax.servlet will break after migration."
52
62
  ),
53
63
  fix_hint="Replace 'javax.servlet' with 'jakarta.servlet' and update the servlet-api dependency.",
64
+ migration_target="jakarta",
65
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxServletToJakartaServlet",
54
66
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.servlet[^;]+);", re.MULTILINE),
55
67
  ),
56
68
  _Rule(
@@ -63,6 +75,8 @@ _IMPORT_RULES: list[_Rule] = [
63
75
  "picked up by the validator after migration."
64
76
  ),
65
77
  fix_hint="Replace 'javax.validation' with 'jakarta.validation'.",
78
+ migration_target="jakarta",
79
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxValidationToJakartaValidation",
66
80
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.validation[^;]+);", re.MULTILINE),
67
81
  ),
68
82
  _Rule(
@@ -75,6 +89,8 @@ _IMPORT_RULES: list[_Rule] = [
75
89
  "will resolve to the wrong class after migration."
76
90
  ),
77
91
  fix_hint="Replace 'javax.transaction' with 'jakarta.transaction'.",
92
+ migration_target="jakarta",
93
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxTransactionToJakartaTransaction",
78
94
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.transaction[^;]+);", re.MULTILINE),
79
95
  ),
80
96
  _Rule(
@@ -86,6 +102,8 @@ _IMPORT_RULES: list[_Rule] = [
86
102
  "@PostConstruct, @PreDestroy, @Resource are affected."
87
103
  ),
88
104
  fix_hint="Replace 'javax.annotation' with 'jakarta.annotation'.",
105
+ migration_target="jakarta",
106
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxAnnotationPackageToJakarta",
89
107
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.annotation[^;]+);", re.MULTILINE),
90
108
  ),
91
109
  _Rule(
@@ -97,6 +115,8 @@ _IMPORT_RULES: list[_Rule] = [
97
115
  "@Inject and @Named from javax.inject are affected."
98
116
  ),
99
117
  fix_hint="Replace 'javax.inject' with 'jakarta.inject'.",
118
+ migration_target="jakarta",
119
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxInjectToJakartaInject",
100
120
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.inject[^;]+);", re.MULTILINE),
101
121
  ),
102
122
  _Rule(
@@ -108,11 +128,30 @@ _IMPORT_RULES: list[_Rule] = [
108
128
  "JAX-RS resource classes, Response, and client code are affected."
109
129
  ),
110
130
  fix_hint="Replace 'javax.ws.rs' with 'jakarta.ws.rs'.",
131
+ migration_target="jakarta",
132
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxWsRsToJakartaWsRs",
111
133
  import_pattern=re.compile(r"^[ \t]*import\s+(javax\.ws\.rs[^;]+);", re.MULTILINE),
112
134
  ),
135
+ _Rule(
136
+ id="MIG-009",
137
+ severity="medium",
138
+ title="javax.jms import — JMS API not migrated to jakarta",
139
+ explanation=(
140
+ "jakarta.jms replaces javax.jms in Jakarta EE 9+. "
141
+ "Message listeners, ConnectionFactory, and Queue references are affected."
142
+ ),
143
+ fix_hint="Replace 'javax.jms' with 'jakarta.jms' and ensure messaging provider supports Jakarta EE 9.",
144
+ migration_target="jakarta",
145
+ openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxJmsToJakartaJms",
146
+ import_pattern=re.compile(r"^[ \t]*import\s+(javax\.jms[^;]+);", re.MULTILINE),
147
+ ),
113
148
  ]
114
149
 
115
- _EXTENDS_RULES: list[_Rule] = [
150
+ # ---------------------------------------------------------------------------
151
+ # Spring Security 6 rules
152
+ # ---------------------------------------------------------------------------
153
+
154
+ _SPRING_SECURITY_RULES: list[_Rule] = [
116
155
  _Rule(
117
156
  id="MIG-005",
118
157
  severity="high",
@@ -126,11 +165,296 @@ _EXTENDS_RULES: list[_Rule] = [
126
165
  "Remove the class extension and expose a SecurityFilterChain @Bean instead. "
127
166
  "See the Spring Security 6 migration guide."
128
167
  ),
168
+ migration_target="spring_security_6",
169
+ openrewrite_recipe="org.openrewrite.java.spring.security6.WebSecurityConfigurerAdapterToSecurityFilterChain",
129
170
  extends_pattern=re.compile(r"\bextends\s+WebSecurityConfigurerAdapter\b"),
130
171
  ),
172
+ _Rule(
173
+ id="MIG-020",
174
+ severity="high",
175
+ title="antMatchers / authorizeRequests — deprecated Spring Security 6 patterns",
176
+ explanation=(
177
+ "antMatchers() was replaced by requestMatchers() and authorizeRequests() was replaced "
178
+ "by authorizeHttpRequests() in Spring Security 6. The old methods are removed. "
179
+ "Also, AuthenticationManagerBuilder-based configuration is superseded by "
180
+ "UserDetailsService and PasswordEncoder beans."
181
+ ),
182
+ fix_hint=(
183
+ "Replace antMatchers() with requestMatchers(), authorizeRequests() with "
184
+ "authorizeHttpRequests(). Migrate HttpSecurity config to Lambda DSL style."
185
+ ),
186
+ migration_target="spring_security_6",
187
+ openrewrite_recipe="org.openrewrite.java.spring.security6.HttpSecurityLambdaDsl",
188
+ code_pattern=re.compile(
189
+ r"\.antMatchers\s*\(|\.authorizeRequests\s*\(\)|"
190
+ r"\bAuthenticationManagerBuilder\b",
191
+ re.MULTILINE,
192
+ ),
193
+ ),
194
+ _Rule(
195
+ id="MIG-019",
196
+ severity="high",
197
+ title="SpringFox / Swagger 2 — incompatible with Spring Boot 3 / Spring MVC 6",
198
+ explanation=(
199
+ "SpringFox (io.springfox) requires Spring MVC internals that were removed in "
200
+ "Spring Framework 6. Applications using @EnableSwagger2 or springfox.documentation "
201
+ "will fail to start after migration to Spring Boot 3."
202
+ ),
203
+ fix_hint=(
204
+ "Migrate to springdoc-openapi-starter-webmvc-ui (OpenAPI 3). "
205
+ "Replace springfox-swagger2 + springfox-swagger-ui dependencies."
206
+ ),
207
+ migration_target="spring_security_6",
208
+ openrewrite_recipe=None,
209
+ import_pattern=re.compile(
210
+ r"^[ \t]*import\s+(springfox\.[^;]+);",
211
+ re.MULTILINE,
212
+ ),
213
+ code_pattern=re.compile(r"@EnableSwagger2\b"),
214
+ ),
131
215
  ]
132
216
 
133
- _ALL_RULES: list[_Rule] = _IMPORT_RULES + _EXTENDS_RULES
217
+ # ---------------------------------------------------------------------------
218
+ # Java 11 — APIs removed from the JDK (must add as explicit deps)
219
+ # ---------------------------------------------------------------------------
220
+
221
+ _JAVA_11_RULES: list[_Rule] = [
222
+ _Rule(
223
+ id="MIG-021",
224
+ severity="high",
225
+ title="javax.xml.bind (JAXB) — removed from JDK in Java 11",
226
+ explanation=(
227
+ "JAXB was part of the JDK in Java 8 (java.xml.bind module) but removed in Java 11. "
228
+ "Code importing javax.xml.bind will fail to compile on Java 11+ unless the "
229
+ "jakarta.xml.bind-api and jaxb-impl artifacts are added as dependencies."
230
+ ),
231
+ fix_hint=(
232
+ "Add 'jakarta.xml.bind:jakarta.xml.bind-api' and 'org.glassfish.jaxb:jaxb-runtime' "
233
+ "as dependencies. Also migrate javax.xml.bind → jakarta.xml.bind for Spring Boot 3."
234
+ ),
235
+ migration_target="java_11",
236
+ openrewrite_recipe=None,
237
+ import_pattern=re.compile(r"^[ \t]*import\s+(javax\.xml\.bind[^;]+);", re.MULTILINE),
238
+ ),
239
+ _Rule(
240
+ id="MIG-022",
241
+ severity="high",
242
+ title="javax.xml.ws (JAX-WS) — removed from JDK in Java 11",
243
+ explanation=(
244
+ "JAX-WS was bundled with the JDK in Java 8 but removed in Java 11. "
245
+ "Applications importing javax.xml.ws require an explicit jaxws-rt dependency."
246
+ ),
247
+ fix_hint=(
248
+ "Add 'com.sun.xml.ws:jaxws-rt' as a dependency. "
249
+ "Also migrate javax.xml.ws → jakarta.xml.ws for Spring Boot 3 targets."
250
+ ),
251
+ migration_target="java_11",
252
+ openrewrite_recipe=None,
253
+ import_pattern=re.compile(r"^[ \t]*import\s+(javax\.xml\.ws[^;]+);", re.MULTILINE),
254
+ ),
255
+ ]
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # Java 15 — Nashorn removed
259
+ # ---------------------------------------------------------------------------
260
+
261
+ _JAVA_15_RULES: list[_Rule] = [
262
+ _Rule(
263
+ id="MIG-012",
264
+ severity="high",
265
+ title="Nashorn ScriptEngine — removed in Java 15",
266
+ explanation=(
267
+ "The Nashorn JavaScript engine was deprecated in Java 11 (JEP 335) and removed "
268
+ "in Java 15 (JEP 372). Code importing jdk.nashorn.* or obtaining the Nashorn engine "
269
+ "via ScriptEngineManager will fail at runtime on Java 15+."
270
+ ),
271
+ fix_hint=(
272
+ "Replace Nashorn with GraalVM Polyglot API (org.graalvm.sdk:polyglot) or "
273
+ "Mozilla Rhino (org.mozilla:rhino). Remove jdk.nashorn.* imports."
274
+ ),
275
+ migration_target="java_15",
276
+ openrewrite_recipe=None,
277
+ import_pattern=re.compile(r"^[ \t]*import\s+(jdk\.nashorn[^;]+);", re.MULTILINE),
278
+ code_pattern=re.compile(r'getEngineByName\s*\(\s*["\']nashorn["\']'),
279
+ ),
280
+ ]
281
+
282
+ # ---------------------------------------------------------------------------
283
+ # Java 17 — SecurityManager removed (JEP 411)
284
+ # ---------------------------------------------------------------------------
285
+
286
+ _JAVA_17_RULES: list[_Rule] = [
287
+ _Rule(
288
+ id="MIG-010",
289
+ severity="critical",
290
+ title="SecurityManager / AccessController — removed in Java 17 (JEP 411)",
291
+ explanation=(
292
+ "The Security Manager and its associated APIs (SecurityManager, AccessController, "
293
+ "System.setSecurityManager, System.getSecurityManager, SecurityPermission, "
294
+ "RuntimePermission) were deprecated for removal in Java 17 and are non-functional. "
295
+ "In Java 17, setSecurityManager() throws UnsupportedOperationException unless "
296
+ "the 'java.security.manager=allow' system property is set."
297
+ ),
298
+ fix_hint=(
299
+ "Remove SecurityManager installation and AccessController.doPrivileged() calls. "
300
+ "Replace with proper module-based access control or Jakarta Security. "
301
+ "See JEP 411 migration guide."
302
+ ),
303
+ migration_target="java_17",
304
+ openrewrite_recipe=None,
305
+ code_pattern=re.compile(
306
+ r"System\.(get|set)SecurityManager\s*\(|"
307
+ r"\bSecurityManager\s+\w+\s*[=;({]|" # variable declaration, requires code-context char to avoid Javadoc FPs
308
+ r"\bnew\s+SecurityManager\s*\(|"
309
+ r"\bextends\s+SecurityManager\b|"
310
+ r"\bAccessController\.(doPrivileged|checkPermission|getContext)\s*\(",
311
+ ),
312
+ ),
313
+ ]
314
+
315
+ # ---------------------------------------------------------------------------
316
+ # Java 9+ — Strong encapsulation (JPMS) — internal APIs
317
+ # ---------------------------------------------------------------------------
318
+
319
+ _JAVA_9_RULES: list[_Rule] = [
320
+ _Rule(
321
+ id="MIG-011",
322
+ severity="high",
323
+ title="JDK internal API imports (sun.* / com.sun.net.*) — strong encapsulation since Java 9",
324
+ explanation=(
325
+ "Imports from sun.* and com.sun.net.* reference JDK-internal APIs that are "
326
+ "not part of the public specification. Since Java 9 (JPMS), these packages are "
327
+ "strongly encapsulated and require --add-exports / --add-opens JVM flags, "
328
+ "which are cumbersome and may be removed in future Java releases."
329
+ ),
330
+ fix_hint=(
331
+ "Replace internal API usage with public equivalents. "
332
+ "For com.sun.net.httpserver, migrate to java.net.http.HttpServer or a framework. "
333
+ "Add '--add-exports java.base/sun.misc=ALL-UNNAMED' only as a last resort."
334
+ ),
335
+ migration_target="java_9_plus",
336
+ openrewrite_recipe=None,
337
+ import_pattern=re.compile(
338
+ r"^[ \t]*import\s+(sun\.[^;]+|com\.sun\.(?:net|tools|jdi|source|management)[^;]+);",
339
+ re.MULTILINE,
340
+ ),
341
+ ),
342
+ _Rule(
343
+ id="MIG-013",
344
+ severity="high",
345
+ title="sun.misc.Unsafe — direct access requires --add-opens since Java 9",
346
+ explanation=(
347
+ "sun.misc.Unsafe is a JDK-internal class not exposed by the public module system. "
348
+ "Accessing it via reflection or direct import requires "
349
+ "'--add-opens java.base/sun.misc=ALL-UNNAMED' on Java 9+. "
350
+ "Many frameworks (ByteBuddy, CGLIB, ASM) use Unsafe internally."
351
+ ),
352
+ fix_hint=(
353
+ "Remove direct Unsafe usage and rely on VarHandle (java.lang.invoke.VarHandle) "
354
+ "as the public replacement. Ensure framework versions used are Java 17+ compatible."
355
+ ),
356
+ migration_target="java_9_plus",
357
+ openrewrite_recipe=None,
358
+ import_pattern=re.compile(
359
+ r"^[ \t]*import\s+(sun\.misc\.Unsafe[^;]*);",
360
+ re.MULTILINE,
361
+ ),
362
+ code_pattern=re.compile(
363
+ r'Unsafe\.getUnsafe\s*\(|"theUnsafe"|getDeclaredField\s*\(\s*["\']theUnsafe["\']',
364
+ ),
365
+ ),
366
+ _Rule(
367
+ id="MIG-014",
368
+ severity="medium",
369
+ title="setAccessible(true) — may throw InaccessibleObjectException on Java 9+",
370
+ explanation=(
371
+ "Reflective access via setAccessible(true) to JDK-internal classes throws "
372
+ "InaccessibleObjectException on Java 9+ unless the owning module grants access. "
373
+ "This is an 'illegal reflective access' warning in Java 9-15 and a hard failure "
374
+ "in Java 17+ for strongly-encapsulated modules."
375
+ ),
376
+ fix_hint=(
377
+ "Ensure setAccessible() calls target application code, not JDK internal classes. "
378
+ "Add necessary '--add-opens' flags for unavoidable cases. "
379
+ "Prefer public APIs to avoid reflection on JDK internals entirely."
380
+ ),
381
+ migration_target="java_9_plus",
382
+ openrewrite_recipe=None,
383
+ code_pattern=re.compile(r"\.setAccessible\s*\(\s*true\s*\)"),
384
+ ),
385
+ ]
386
+
387
+ # ---------------------------------------------------------------------------
388
+ # Java 18+ — finalize() deprecated for removal
389
+ # ---------------------------------------------------------------------------
390
+
391
+ _JAVA_18_RULES: list[_Rule] = [
392
+ _Rule(
393
+ id="MIG-015",
394
+ severity="medium",
395
+ title="finalize() override — deprecated for removal since Java 9, removed in Java 18",
396
+ explanation=(
397
+ "Object.finalize() was deprecated in Java 9 and deprecated-for-removal in Java 18 "
398
+ "(JEP 421). Overriding finalize() is unreliable, may delay GC, and the mechanism "
399
+ "is being removed from the platform. Java 18+ emits warnings; future JDK versions "
400
+ "will not call finalizers."
401
+ ),
402
+ fix_hint=(
403
+ "Replace finalize() with try-with-resources (AutoCloseable/Closeable) or "
404
+ "java.lang.ref.Cleaner for resource cleanup."
405
+ ),
406
+ migration_target="java_18_plus",
407
+ openrewrite_recipe=None,
408
+ code_pattern=re.compile(
409
+ r"\b(?:protected|public)\s+void\s+finalize\s*\(\s*\)",
410
+ ),
411
+ ),
412
+ ]
413
+
414
+ # ---------------------------------------------------------------------------
415
+ # Best-practice / low-severity — legacy date/time API
416
+ # ---------------------------------------------------------------------------
417
+
418
+ _LEGACY_API_RULES: list[_Rule] = [
419
+ _Rule(
420
+ id="MIG-016",
421
+ severity="low",
422
+ title="Legacy date/time API (java.util.Date / Calendar / SimpleDateFormat)",
423
+ explanation=(
424
+ "java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat are "
425
+ "thread-unsafe and error-prone. They are superseded by java.time (JSR-310) "
426
+ "introduced in Java 8. While not removed, they cause issues in multi-threaded "
427
+ "Spring applications and should be migrated before upgrading."
428
+ ),
429
+ fix_hint=(
430
+ "Replace Date with LocalDate/LocalDateTime/ZonedDateTime, "
431
+ "Calendar with java.time.Calendar, "
432
+ "SimpleDateFormat with DateTimeFormatter (thread-safe)."
433
+ ),
434
+ migration_target="java_8_best_practice",
435
+ openrewrite_recipe="org.openrewrite.java.migrate.JavaTimeAPIs",
436
+ import_pattern=re.compile(
437
+ r"^[ \t]*import\s+(java\.util\.(?:Date|Calendar|GregorianCalendar)"
438
+ r"|java\.text\.(?:SimpleDateFormat|DateFormat))[^;]*;",
439
+ re.MULTILINE,
440
+ ),
441
+ ),
442
+ ]
443
+
444
+ # ---------------------------------------------------------------------------
445
+ # All rules list
446
+ # ---------------------------------------------------------------------------
447
+
448
+ _ALL_RULES: list[_Rule] = (
449
+ _JAKARTA_RULES
450
+ + _SPRING_SECURITY_RULES
451
+ + _JAVA_11_RULES
452
+ + _JAVA_15_RULES
453
+ + _JAVA_17_RULES
454
+ + _JAVA_9_RULES
455
+ + _JAVA_18_RULES
456
+ + _LEGACY_API_RULES
457
+ )
134
458
 
135
459
  SEVERITY_ORDER: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low": 3}
136
460
 
@@ -142,14 +466,16 @@ SEVERITY_ORDER: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low":
142
466
  @dataclass
143
467
  class MigrationFinding:
144
468
  id: str # deterministic: "{rule_id}-{file_hash[:12]}"
145
- rule_id: str # "MIG-001" .. "MIG-008"
469
+ rule_id: str # "MIG-001" .. "MIG-022"
146
470
  severity: str # "critical" | "high" | "medium" | "low"
147
471
  title: str
148
472
  source_file: str # relative path
149
473
  first_line: int # 1-based line number of first match
150
- imports_found: list[str] = field(default_factory=list) # matched import statements
474
+ imports_found: list[str] = field(default_factory=list) # matched import stmts or code snippets
151
475
  explanation: str = ""
152
476
  fix_hint: str = ""
477
+ migration_target: str = ""
478
+ openrewrite_recipe: Optional[str] = None
153
479
 
154
480
  @staticmethod
155
481
  def make_id(rule_id: str, source_file: str) -> str:
@@ -166,9 +492,12 @@ class MigrationFinding:
166
492
  "first_line": self.first_line,
167
493
  "explanation": self.explanation,
168
494
  "fix_hint": self.fix_hint,
495
+ "migration_target": self.migration_target,
169
496
  }
170
497
  if self.imports_found:
171
498
  d["imports_found"] = self.imports_found
499
+ if self.openrewrite_recipe:
500
+ d["openrewrite_recipe"] = self.openrewrite_recipe
172
501
  return d
173
502
 
174
503
 
@@ -178,7 +507,7 @@ class MigrationFinding:
178
507
 
179
508
  @dataclass
180
509
  class MigrationReport:
181
- schema_version: str = "1.0"
510
+ schema_version: str = "1.1"
182
511
  generated_at: str = ""
183
512
  repo_id: str = ""
184
513
  git_head: str = ""
@@ -200,11 +529,13 @@ class MigrationReport:
200
529
 
201
530
  by_severity: dict[str, int] = {"critical": 0, "high": 0, "medium": 0, "low": 0}
202
531
  by_rule: dict[str, int] = {}
532
+ by_target: dict[str, int] = {}
203
533
  affected_files: set[str] = set()
204
534
 
205
535
  for f in self.findings:
206
536
  by_severity[f.severity] = by_severity.get(f.severity, 0) + 1
207
537
  by_rule[f.rule_id] = by_rule.get(f.rule_id, 0) + 1
538
+ by_target[f.migration_target] = by_target.get(f.migration_target, 0) + 1
208
539
  affected_files.add(f.source_file)
209
540
 
210
541
  self.blocking_count = by_severity["critical"] + by_severity["high"]
@@ -247,6 +578,7 @@ class MigrationReport:
247
578
  "affected_files": len(affected_files),
248
579
  "by_severity": by_severity,
249
580
  "by_rule": by_rule,
581
+ "by_migration_target": by_target,
250
582
  }
251
583
  return self
252
584
 
@@ -287,9 +619,12 @@ class MigrationReport:
287
619
  for f in sorted(visible, key=lambda x: (SEVERITY_ORDER.get(x.severity, 3), x.source_file)):
288
620
  lines.append(
289
621
  f"{f.rule_id} [{f.severity.upper()}] {f.source_file}:{f.first_line}"
622
+ f" [{f.migration_target}]"
290
623
  )
291
624
  lines.append(f" {f.title}")
292
625
  lines.append(f" Fix: {f.fix_hint}")
626
+ if f.openrewrite_recipe:
627
+ lines.append(f" OpenRewrite: {f.openrewrite_recipe}")
293
628
  lines.append("")
294
629
 
295
630
  return "\n".join(lines)
@@ -307,44 +642,58 @@ def _scan_file(
307
642
  findings: list[MigrationFinding] = []
308
643
 
309
644
  for rule in rules:
645
+ # An import_pattern and code_pattern can coexist on the same rule (OR semantics).
646
+ # A finding is created if EITHER matches; we report the earliest match position.
647
+ matched_imports: list[str] = []
648
+ import_first_line: Optional[int] = None
649
+ code_first_line: Optional[int] = None
650
+ code_snippets: list[str] = []
651
+
310
652
  if rule.import_pattern is not None:
311
653
  matches = list(rule.import_pattern.finditer(source))
312
- if not matches:
313
- continue
314
- # Compute 1-based line number of first match
315
- first_line = source[: matches[0].start()].count("\n") + 1
316
- imports_found = [m.group(1) for m in matches]
317
- findings.append(
318
- MigrationFinding(
319
- id=MigrationFinding.make_id(rule.id, rel_path),
320
- rule_id=rule.id,
321
- severity=rule.severity,
322
- title=rule.title,
323
- source_file=rel_path,
324
- first_line=first_line,
325
- imports_found=imports_found,
326
- explanation=rule.explanation,
327
- fix_hint=rule.fix_hint,
328
- )
329
- )
330
-
331
- elif rule.extends_pattern is not None:
654
+ if matches:
655
+ import_first_line = source[: matches[0].start()].count("\n") + 1
656
+ matched_imports = [m.group(1) for m in matches]
657
+
658
+ if rule.code_pattern is not None:
659
+ m = rule.code_pattern.search(source)
660
+ if m is not None:
661
+ code_first_line = source[: m.start()].count("\n") + 1
662
+ code_snippets = [m.group(0).strip()]
663
+
664
+ # extends_pattern is a legacy form of code_pattern
665
+ extends_first_line: Optional[int] = None
666
+ if rule.extends_pattern is not None:
332
667
  m = rule.extends_pattern.search(source)
333
- if m is None:
334
- continue
335
- first_line = source[: m.start()].count("\n") + 1
336
- findings.append(
337
- MigrationFinding(
338
- id=MigrationFinding.make_id(rule.id, rel_path),
339
- rule_id=rule.id,
340
- severity=rule.severity,
341
- title=rule.title,
342
- source_file=rel_path,
343
- first_line=first_line,
344
- explanation=rule.explanation,
345
- fix_hint=rule.fix_hint,
346
- )
668
+ if m is not None:
669
+ extends_first_line = source[: m.start()].count("\n") + 1
670
+
671
+ # Determine overall match
672
+ candidate_lines = [
673
+ ln for ln in (import_first_line, code_first_line, extends_first_line)
674
+ if ln is not None
675
+ ]
676
+ if not candidate_lines:
677
+ continue
678
+
679
+ first_line = min(candidate_lines)
680
+ all_matches = matched_imports + code_snippets
681
+
682
+ findings.append(
683
+ MigrationFinding(
684
+ id=MigrationFinding.make_id(rule.id, rel_path),
685
+ rule_id=rule.id,
686
+ severity=rule.severity,
687
+ title=rule.title,
688
+ source_file=rel_path,
689
+ first_line=first_line,
690
+ imports_found=all_matches,
691
+ explanation=rule.explanation,
692
+ fix_hint=rule.fix_hint,
693
+ migration_target=rule.migration_target,
694
+ openrewrite_recipe=rule.openrewrite_recipe,
347
695
  )
696
+ )
348
697
 
349
698
  return findings
350
699
 
@@ -359,7 +708,7 @@ def run_migrate_check(
359
708
  *,
360
709
  min_severity: str = "low",
361
710
  ) -> MigrationReport:
362
- """Scan Java files for Spring Boot 2→3 migration blockers.
711
+ """Scan Java files for migration blockers (Spring Boot 2→3, Java 8→17/21).
363
712
 
364
713
  Args:
365
714
  file_paths: Relative Java file paths (from find_java_files).
@@ -368,7 +717,8 @@ def run_migrate_check(
368
717
  from the report. Choices: critical | high | medium | low.
369
718
 
370
719
  Returns:
371
- MigrationReport with findings, readiness_score, and effort estimate.
720
+ MigrationReport with findings, readiness_score, effort estimate, and
721
+ migration_target breakdown.
372
722
  """
373
723
  min_order = SEVERITY_ORDER.get(min_severity, 3)
374
724
  all_findings: list[MigrationFinding] = []
@@ -391,6 +741,8 @@ def run_migrate_check(
391
741
  if read_errors:
392
742
  limitations.append(f"{read_errors} file(s) could not be read and were skipped.")
393
743
 
744
+ limitations.extend(_STATIC_LIMITATIONS)
745
+
394
746
  # Detect Spring Boot 2 pom.xml heuristic (best-effort, non-fatal)
395
747
  spring_boot_2 = _detect_spring_boot_2(root)
396
748
 
@@ -420,9 +772,31 @@ def run_migrate_check(
420
772
  return report.finalize()
421
773
 
422
774
 
775
+ # Items that static analysis cannot determine, always emitted as limitations
776
+ _STATIC_LIMITATIONS: list[str] = [
777
+ "Thread.stop/suspend/resume deprecation: cannot reliably detect without type resolution "
778
+ "(requires knowing that a variable is typed as java.lang.Thread).",
779
+ "CORBA removal (Java 11): org.omg.* usage not scanned; add manually if project uses CORBA.",
780
+ "Module compatibility (JPMS): --add-opens requirements cannot be determined without "
781
+ "running the application against the target JDK.",
782
+ "Transitive dependency compatibility: library versions (Hibernate, Jackson, etc.) must be "
783
+ "verified separately against Spring Boot 3 BOM.",
784
+ "XML-based Spring config (applicationContext.xml, web.xml): not scanned — bean class names "
785
+ "and servlet filter chains in XML may reference javax.* classes.",
786
+ "Runtime proxy behaviour (CGLIB/ByteBuddy subclass proxies): compatibility with Java 17+ "
787
+ "strong encapsulation depends on framework version, not detectable via import scanning.",
788
+ ]
789
+
790
+
423
791
  def _detect_spring_boot_2(root: Path) -> bool:
424
792
  """Return True if any pom.xml or build.gradle declares spring-boot 2.x."""
425
- _SB2 = re.compile(r"spring[-.]boot[^\"'\n]*[\"']?2\.\d+", re.IGNORECASE)
793
+ _SB2 = re.compile(
794
+ r"(?:spring[.\-]boot[.\-]?(?:version|starter|parent)[^=\n]*[=:\s>\"']?\s*)"
795
+ r"2\.\d+[\.\d]*|"
796
+ r"<version>\s*2\.\d+[\.\d]*\s*</version>.*spring.boot|"
797
+ r"spring.boot.*<version>\s*2\.\d+",
798
+ re.IGNORECASE | re.DOTALL,
799
+ )
426
800
  for name in ("pom.xml", "build.gradle", "build.gradle.kts"):
427
801
  candidate = root / name
428
802
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.35.23
3
+ Version: 1.35.24
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
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  **Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
42
42
 
43
- ![Version](https://img.shields.io/badge/version-1.35.23-blue)
43
+ ![Version](https://img.shields.io/badge/version-1.35.24-blue)
44
44
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
45
45
 
46
46
  ---
@@ -114,7 +114,7 @@ pipx install sourcecode
114
114
 
115
115
  ```bash
116
116
  sourcecode version
117
- # sourcecode 1.35.23
117
+ # sourcecode 1.35.24
118
118
  ```
119
119
 
120
120
  ---
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=_3RZgCJrBxKTnf4cMy8WI2Ca9TZsi_5ac6JucEkUIVM,104
1
+ sourcecode/__init__.py,sha256=2yK-pjDWylHCv6BnJnDbJ6apRllmWf4VbNJHpb2m3Ec,104
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
4
4
  sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
@@ -29,7 +29,7 @@ sourcecode/graph_analyzer.py,sha256=DHR8fY69oU_Pi4SYaWboX6EoEFrctQKB9dsjpqwGMzw,
29
29
  sourcecode/license.py,sha256=3JCV2OeTVttKrOGBguU5uZC0c02Stig-KLB0mP2lNiY,22742
30
30
  sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
31
31
  sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
32
- sourcecode/migrate_check.py,sha256=5vsO7YJiXkn6HKealy2n7qoc99-eD2EW9YAE8Jm1HR0,16341
32
+ sourcecode/migrate_check.py,sha256=aJMpsfiS9D8FKTyfiLAh0BylAAk178ZsWpadJffVpaY,33794
33
33
  sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
34
34
  sourcecode/path_filters.py,sha256=ROFRQ8eSLBEMiixK9f45-RO7um4VEEcjoD5AA4I427I,3739
35
35
  sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
@@ -94,8 +94,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
94
94
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
95
95
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
96
96
  sourcecode/telemetry/transport.py,sha256=QSslxIwij8YkRWcVvxykODDrkiN_GAAEu3dUP7KIWeE,1651
97
- sourcecode-1.35.23.dist-info/METADATA,sha256=SPVcfkrBpRzffHQfMhEbeyM4fxShVvN6gdmSMOJs35Q,21297
98
- sourcecode-1.35.23.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
99
- sourcecode-1.35.23.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
100
- sourcecode-1.35.23.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
101
- sourcecode-1.35.23.dist-info/RECORD,,
97
+ sourcecode-1.35.24.dist-info/METADATA,sha256=gCMQSuUMZ5yET1UzAxaflOMZK-yra4U_lSogJ12PC7o,21297
98
+ sourcecode-1.35.24.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
99
+ sourcecode-1.35.24.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
100
+ sourcecode-1.35.24.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
101
+ sourcecode-1.35.24.dist-info/RECORD,,