sourcecode 1.35.23__py3-none-any.whl → 1.35.25__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/migrate_check.py +909 -64
- {sourcecode-1.35.23.dist-info → sourcecode-1.35.25.dist-info}/METADATA +3 -3
- {sourcecode-1.35.23.dist-info → sourcecode-1.35.25.dist-info}/RECORD +7 -7
- {sourcecode-1.35.23.dist-info → sourcecode-1.35.25.dist-info}/WHEEL +0 -0
- {sourcecode-1.35.23.dist-info → sourcecode-1.35.25.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.35.23.dist-info → sourcecode-1.35.25.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/migrate_check.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
"""migrate_check.py — Spring Boot 2
|
|
1
|
+
"""migrate_check.py — Java 8/Spring Boot 2 migration readiness checker.
|
|
2
2
|
|
|
3
|
-
Scans Java source files
|
|
4
|
-
|
|
3
|
+
Scans Java source files, Spring XML config, and build descriptors for patterns
|
|
4
|
+
that must be addressed when migrating:
|
|
5
|
+
- Spring Boot 2 → 3 (javax → jakarta, Spring Security 6)
|
|
6
|
+
- Java 8 → 17 / 21 (SecurityManager, Nashorn, Unsafe, reflection, etc.)
|
|
7
|
+
- XML Spring config (applicationContext.xml, web.xml, security XML)
|
|
8
|
+
- Dependency incompatibilities (SpringFox, Hibernate 5, ByteBuddy old)
|
|
5
9
|
|
|
6
10
|
Entry point: run_migrate_check(file_paths, root) → MigrationReport
|
|
7
11
|
"""
|
|
8
12
|
from __future__ import annotations
|
|
9
13
|
|
|
14
|
+
import fnmatch
|
|
10
15
|
import hashlib
|
|
16
|
+
import os
|
|
11
17
|
import re
|
|
12
18
|
from dataclasses import dataclass, field
|
|
13
19
|
from datetime import datetime, timezone
|
|
@@ -16,7 +22,7 @@ from typing import Optional
|
|
|
16
22
|
|
|
17
23
|
|
|
18
24
|
# ---------------------------------------------------------------------------
|
|
19
|
-
# Rule catalogue
|
|
25
|
+
# Rule catalogue — Java source rules
|
|
20
26
|
# ---------------------------------------------------------------------------
|
|
21
27
|
|
|
22
28
|
@dataclass(frozen=True)
|
|
@@ -26,11 +32,18 @@ class _Rule:
|
|
|
26
32
|
title: str
|
|
27
33
|
explanation: str
|
|
28
34
|
fix_hint: str
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
migration_target: str = "spring_boot_3"
|
|
36
|
+
openrewrite_recipe: Optional[str] = None
|
|
37
|
+
import_pattern: Optional[re.Pattern] = None
|
|
38
|
+
extends_pattern: Optional[re.Pattern] = None
|
|
39
|
+
code_pattern: Optional[re.Pattern] = None
|
|
31
40
|
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Jakarta namespace rules (Spring Boot 2 → 3)
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
_JAKARTA_RULES: list[_Rule] = [
|
|
34
47
|
_Rule(
|
|
35
48
|
id="MIG-001",
|
|
36
49
|
severity="critical",
|
|
@@ -40,6 +53,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
40
53
|
"namespace. Files importing javax.persistence will not compile after migration."
|
|
41
54
|
),
|
|
42
55
|
fix_hint="Replace 'javax.persistence' with 'jakarta.persistence' across all affected files.",
|
|
56
|
+
migration_target="jakarta",
|
|
57
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence",
|
|
43
58
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.persistence[^;]+);", re.MULTILINE),
|
|
44
59
|
),
|
|
45
60
|
_Rule(
|
|
@@ -51,6 +66,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
51
66
|
"HttpServletResponse referencing javax.servlet will break after migration."
|
|
52
67
|
),
|
|
53
68
|
fix_hint="Replace 'javax.servlet' with 'jakarta.servlet' and update the servlet-api dependency.",
|
|
69
|
+
migration_target="jakarta",
|
|
70
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxServletToJakartaServlet",
|
|
54
71
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.servlet[^;]+);", re.MULTILINE),
|
|
55
72
|
),
|
|
56
73
|
_Rule(
|
|
@@ -63,6 +80,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
63
80
|
"picked up by the validator after migration."
|
|
64
81
|
),
|
|
65
82
|
fix_hint="Replace 'javax.validation' with 'jakarta.validation'.",
|
|
83
|
+
migration_target="jakarta",
|
|
84
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxValidationToJakartaValidation",
|
|
66
85
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.validation[^;]+);", re.MULTILINE),
|
|
67
86
|
),
|
|
68
87
|
_Rule(
|
|
@@ -75,6 +94,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
75
94
|
"will resolve to the wrong class after migration."
|
|
76
95
|
),
|
|
77
96
|
fix_hint="Replace 'javax.transaction' with 'jakarta.transaction'.",
|
|
97
|
+
migration_target="jakarta",
|
|
98
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxTransactionToJakartaTransaction",
|
|
78
99
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.transaction[^;]+);", re.MULTILINE),
|
|
79
100
|
),
|
|
80
101
|
_Rule(
|
|
@@ -86,6 +107,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
86
107
|
"@PostConstruct, @PreDestroy, @Resource are affected."
|
|
87
108
|
),
|
|
88
109
|
fix_hint="Replace 'javax.annotation' with 'jakarta.annotation'.",
|
|
110
|
+
migration_target="jakarta",
|
|
111
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxAnnotationPackageToJakarta",
|
|
89
112
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.annotation[^;]+);", re.MULTILINE),
|
|
90
113
|
),
|
|
91
114
|
_Rule(
|
|
@@ -97,6 +120,8 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
97
120
|
"@Inject and @Named from javax.inject are affected."
|
|
98
121
|
),
|
|
99
122
|
fix_hint="Replace 'javax.inject' with 'jakarta.inject'.",
|
|
123
|
+
migration_target="jakarta",
|
|
124
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxInjectToJakartaInject",
|
|
100
125
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.inject[^;]+);", re.MULTILINE),
|
|
101
126
|
),
|
|
102
127
|
_Rule(
|
|
@@ -108,11 +133,30 @@ _IMPORT_RULES: list[_Rule] = [
|
|
|
108
133
|
"JAX-RS resource classes, Response, and client code are affected."
|
|
109
134
|
),
|
|
110
135
|
fix_hint="Replace 'javax.ws.rs' with 'jakarta.ws.rs'.",
|
|
136
|
+
migration_target="jakarta",
|
|
137
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxWsRsToJakartaWsRs",
|
|
111
138
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.ws\.rs[^;]+);", re.MULTILINE),
|
|
112
139
|
),
|
|
140
|
+
_Rule(
|
|
141
|
+
id="MIG-009",
|
|
142
|
+
severity="medium",
|
|
143
|
+
title="javax.jms import — JMS API not migrated to jakarta",
|
|
144
|
+
explanation=(
|
|
145
|
+
"jakarta.jms replaces javax.jms in Jakarta EE 9+. "
|
|
146
|
+
"Message listeners, ConnectionFactory, and Queue references are affected."
|
|
147
|
+
),
|
|
148
|
+
fix_hint="Replace 'javax.jms' with 'jakarta.jms' and ensure messaging provider supports Jakarta EE 9.",
|
|
149
|
+
migration_target="jakarta",
|
|
150
|
+
openrewrite_recipe="org.openrewrite.java.migrate.jakarta.JavaxJmsToJakartaJms",
|
|
151
|
+
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.jms[^;]+);", re.MULTILINE),
|
|
152
|
+
),
|
|
113
153
|
]
|
|
114
154
|
|
|
115
|
-
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Spring Security 6 rules
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
_SPRING_SECURITY_RULES: list[_Rule] = [
|
|
116
160
|
_Rule(
|
|
117
161
|
id="MIG-005",
|
|
118
162
|
severity="high",
|
|
@@ -126,30 +170,753 @@ _EXTENDS_RULES: list[_Rule] = [
|
|
|
126
170
|
"Remove the class extension and expose a SecurityFilterChain @Bean instead. "
|
|
127
171
|
"See the Spring Security 6 migration guide."
|
|
128
172
|
),
|
|
173
|
+
migration_target="spring_security_6",
|
|
174
|
+
openrewrite_recipe="org.openrewrite.java.spring.security6.WebSecurityConfigurerAdapterToSecurityFilterChain",
|
|
129
175
|
extends_pattern=re.compile(r"\bextends\s+WebSecurityConfigurerAdapter\b"),
|
|
130
176
|
),
|
|
177
|
+
_Rule(
|
|
178
|
+
id="MIG-020",
|
|
179
|
+
severity="high",
|
|
180
|
+
title="antMatchers / authorizeRequests — deprecated Spring Security 6 patterns",
|
|
181
|
+
explanation=(
|
|
182
|
+
"antMatchers() was replaced by requestMatchers() and authorizeRequests() was replaced "
|
|
183
|
+
"by authorizeHttpRequests() in Spring Security 6. The old methods are removed. "
|
|
184
|
+
"Also, AuthenticationManagerBuilder-based configuration is superseded by "
|
|
185
|
+
"UserDetailsService and PasswordEncoder beans."
|
|
186
|
+
),
|
|
187
|
+
fix_hint=(
|
|
188
|
+
"Replace antMatchers() with requestMatchers(), authorizeRequests() with "
|
|
189
|
+
"authorizeHttpRequests(). Migrate HttpSecurity config to Lambda DSL style."
|
|
190
|
+
),
|
|
191
|
+
migration_target="spring_security_6",
|
|
192
|
+
openrewrite_recipe="org.openrewrite.java.spring.security6.HttpSecurityLambdaDsl",
|
|
193
|
+
code_pattern=re.compile(
|
|
194
|
+
r"\.antMatchers\s*\(|\.authorizeRequests\s*\(\)|"
|
|
195
|
+
r"\bAuthenticationManagerBuilder\b",
|
|
196
|
+
re.MULTILINE,
|
|
197
|
+
),
|
|
198
|
+
),
|
|
199
|
+
_Rule(
|
|
200
|
+
id="MIG-019",
|
|
201
|
+
severity="high",
|
|
202
|
+
title="SpringFox / Swagger 2 — incompatible with Spring Boot 3 / Spring MVC 6",
|
|
203
|
+
explanation=(
|
|
204
|
+
"SpringFox (io.springfox) requires Spring MVC internals that were removed in "
|
|
205
|
+
"Spring Framework 6. Applications using @EnableSwagger2 or springfox.documentation "
|
|
206
|
+
"will fail to start after migration to Spring Boot 3."
|
|
207
|
+
),
|
|
208
|
+
fix_hint=(
|
|
209
|
+
"Migrate to springdoc-openapi-starter-webmvc-ui (OpenAPI 3). "
|
|
210
|
+
"Replace springfox-swagger2 + springfox-swagger-ui dependencies."
|
|
211
|
+
),
|
|
212
|
+
migration_target="spring_security_6",
|
|
213
|
+
openrewrite_recipe=None,
|
|
214
|
+
import_pattern=re.compile(
|
|
215
|
+
r"^[ \t]*import\s+(springfox\.[^;]+);",
|
|
216
|
+
re.MULTILINE,
|
|
217
|
+
),
|
|
218
|
+
code_pattern=re.compile(r"@EnableSwagger2\b"),
|
|
219
|
+
),
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
# Java 11 — APIs removed from the JDK
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
_JAVA_11_RULES: list[_Rule] = [
|
|
227
|
+
_Rule(
|
|
228
|
+
id="MIG-021",
|
|
229
|
+
severity="high",
|
|
230
|
+
title="javax.xml.bind (JAXB) — removed from JDK in Java 11",
|
|
231
|
+
explanation=(
|
|
232
|
+
"JAXB was part of the JDK in Java 8 (java.xml.bind module) but removed in Java 11. "
|
|
233
|
+
"Code importing javax.xml.bind will fail to compile on Java 11+ unless the "
|
|
234
|
+
"jakarta.xml.bind-api and jaxb-impl artifacts are added as dependencies."
|
|
235
|
+
),
|
|
236
|
+
fix_hint=(
|
|
237
|
+
"Add 'jakarta.xml.bind:jakarta.xml.bind-api' and 'org.glassfish.jaxb:jaxb-runtime' "
|
|
238
|
+
"as dependencies. Also migrate javax.xml.bind → jakarta.xml.bind for Spring Boot 3."
|
|
239
|
+
),
|
|
240
|
+
migration_target="java_11",
|
|
241
|
+
openrewrite_recipe=None,
|
|
242
|
+
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.xml\.bind[^;]+);", re.MULTILINE),
|
|
243
|
+
),
|
|
244
|
+
_Rule(
|
|
245
|
+
id="MIG-022",
|
|
246
|
+
severity="high",
|
|
247
|
+
title="javax.xml.ws (JAX-WS) — removed from JDK in Java 11",
|
|
248
|
+
explanation=(
|
|
249
|
+
"JAX-WS was bundled with the JDK in Java 8 but removed in Java 11. "
|
|
250
|
+
"Applications importing javax.xml.ws require an explicit jaxws-rt dependency."
|
|
251
|
+
),
|
|
252
|
+
fix_hint=(
|
|
253
|
+
"Add 'com.sun.xml.ws:jaxws-rt' as a dependency. "
|
|
254
|
+
"Also migrate javax.xml.ws → jakarta.xml.ws for Spring Boot 3 targets."
|
|
255
|
+
),
|
|
256
|
+
migration_target="java_11",
|
|
257
|
+
openrewrite_recipe=None,
|
|
258
|
+
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.xml\.ws[^;]+);", re.MULTILINE),
|
|
259
|
+
),
|
|
260
|
+
_Rule(
|
|
261
|
+
id="MIG-023",
|
|
262
|
+
severity="critical",
|
|
263
|
+
title="CORBA APIs (org.omg.* / javax.rmi.*) — removed from JDK in Java 11",
|
|
264
|
+
explanation=(
|
|
265
|
+
"The CORBA APIs (org.omg.* and javax.rmi.CORBA / javax.rmi.ssl) were deprecated "
|
|
266
|
+
"in Java 9 (JEP 289) and removed from the JDK in Java 11 (JEP 320). Applications "
|
|
267
|
+
"importing these packages will fail to compile or run on Java 11+ unless the "
|
|
268
|
+
"'org.glassfish.corba:glassfish-corba-omgapi' artifact is added explicitly."
|
|
269
|
+
),
|
|
270
|
+
fix_hint=(
|
|
271
|
+
"Remove CORBA usage where possible — CORBA is effectively dead technology. "
|
|
272
|
+
"If CORBA interop is unavoidable, add 'org.glassfish.corba:glassfish-corba-omgapi' "
|
|
273
|
+
"as an explicit Maven/Gradle dependency."
|
|
274
|
+
),
|
|
275
|
+
migration_target="java_11",
|
|
276
|
+
openrewrite_recipe=None,
|
|
277
|
+
import_pattern=re.compile(
|
|
278
|
+
r"^[ \t]*import\s+(org\.omg\.[^;]+|javax\.rmi\.CORBA\.[^;]+|javax\.rmi\.ssl\.[^;]+);",
|
|
279
|
+
re.MULTILINE,
|
|
280
|
+
),
|
|
281
|
+
),
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
# Java 15 — Nashorn removed
|
|
286
|
+
# ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
_JAVA_15_RULES: list[_Rule] = [
|
|
289
|
+
_Rule(
|
|
290
|
+
id="MIG-012",
|
|
291
|
+
severity="high",
|
|
292
|
+
title="Nashorn ScriptEngine — removed in Java 15",
|
|
293
|
+
explanation=(
|
|
294
|
+
"The Nashorn JavaScript engine was deprecated in Java 11 (JEP 335) and removed "
|
|
295
|
+
"in Java 15 (JEP 372). Code importing jdk.nashorn.* or obtaining the Nashorn engine "
|
|
296
|
+
"via ScriptEngineManager will fail at runtime on Java 15+."
|
|
297
|
+
),
|
|
298
|
+
fix_hint=(
|
|
299
|
+
"Replace Nashorn with GraalVM Polyglot API (org.graalvm.sdk:polyglot) or "
|
|
300
|
+
"Mozilla Rhino (org.mozilla:rhino). Remove jdk.nashorn.* imports."
|
|
301
|
+
),
|
|
302
|
+
migration_target="java_15",
|
|
303
|
+
openrewrite_recipe=None,
|
|
304
|
+
import_pattern=re.compile(r"^[ \t]*import\s+(jdk\.nashorn[^;]+);", re.MULTILINE),
|
|
305
|
+
code_pattern=re.compile(r'getEngineByName\s*\(\s*["\']nashorn["\']'),
|
|
306
|
+
),
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
# Java 17 — SecurityManager removed (JEP 411), Thread deprecated methods
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
_JAVA_17_RULES: list[_Rule] = [
|
|
314
|
+
_Rule(
|
|
315
|
+
id="MIG-010",
|
|
316
|
+
severity="critical",
|
|
317
|
+
title="SecurityManager / AccessController — removed in Java 17 (JEP 411)",
|
|
318
|
+
explanation=(
|
|
319
|
+
"The Security Manager and its associated APIs (SecurityManager, AccessController, "
|
|
320
|
+
"System.setSecurityManager, System.getSecurityManager, SecurityPermission, "
|
|
321
|
+
"RuntimePermission) were deprecated for removal in Java 17 and are non-functional. "
|
|
322
|
+
"In Java 17, setSecurityManager() throws UnsupportedOperationException unless "
|
|
323
|
+
"the 'java.security.manager=allow' system property is set."
|
|
324
|
+
),
|
|
325
|
+
fix_hint=(
|
|
326
|
+
"Remove SecurityManager installation and AccessController.doPrivileged() calls. "
|
|
327
|
+
"Replace with proper module-based access control or Jakarta Security. "
|
|
328
|
+
"See JEP 411 migration guide."
|
|
329
|
+
),
|
|
330
|
+
migration_target="java_17",
|
|
331
|
+
openrewrite_recipe=None,
|
|
332
|
+
code_pattern=re.compile(
|
|
333
|
+
r"System\.(get|set)SecurityManager\s*\(|"
|
|
334
|
+
r"\bSecurityManager\s+\w+\s*[=;({]|"
|
|
335
|
+
r"\bnew\s+SecurityManager\s*\(|"
|
|
336
|
+
r"\bextends\s+SecurityManager\b|"
|
|
337
|
+
r"\bAccessController\.(doPrivileged|checkPermission|getContext)\s*\(",
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
_Rule(
|
|
341
|
+
id="MIG-024",
|
|
342
|
+
severity="medium",
|
|
343
|
+
title="Thread.stop / Thread.suspend / Thread.resume — deprecated for removal (Java 17+)",
|
|
344
|
+
explanation=(
|
|
345
|
+
"Thread.stop(), Thread.suspend(), and Thread.resume() are deprecated since Java 1.2 "
|
|
346
|
+
"and deprecated-for-removal since Java 17 (JEP 411 scope). Thread.stop() is "
|
|
347
|
+
"inherently unsafe — it throws ThreadDeath which can corrupt object state. "
|
|
348
|
+
"Thread.suspend/resume cause deadlocks when the suspended thread holds a monitor. "
|
|
349
|
+
"Note: detection is best-effort; confirm the variable type is java.lang.Thread."
|
|
350
|
+
),
|
|
351
|
+
fix_hint=(
|
|
352
|
+
"Use Thread.interrupt() with InterruptedException for cooperative cancellation. "
|
|
353
|
+
"Replace suspend/resume patterns with wait()/notify(), Semaphore, or a higher-level "
|
|
354
|
+
"concurrency abstraction (BlockingQueue, CountDownLatch, etc.)."
|
|
355
|
+
),
|
|
356
|
+
migration_target="java_17",
|
|
357
|
+
openrewrite_recipe=None,
|
|
358
|
+
code_pattern=re.compile(
|
|
359
|
+
r"\b(?:thread|[a-zA-Z]\w*[Tt]hread)\.(stop|suspend|resume)\s*\("
|
|
360
|
+
r"|\bnew\s+Thread\s*\([^)]{0,120}\)\s*\.(stop|suspend|resume)\s*\("
|
|
361
|
+
r"|\bThread\.currentThread\s*\(\)\s*\.(stop|suspend|resume)\s*\(",
|
|
362
|
+
re.MULTILINE,
|
|
363
|
+
),
|
|
364
|
+
),
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
# Java 9+ — Strong encapsulation (JPMS) — internal APIs
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
_JAVA_9_RULES: list[_Rule] = [
|
|
372
|
+
_Rule(
|
|
373
|
+
id="MIG-011",
|
|
374
|
+
severity="high",
|
|
375
|
+
title="JDK internal API imports (sun.* / com.sun.net.*) — strong encapsulation since Java 9",
|
|
376
|
+
explanation=(
|
|
377
|
+
"Imports from sun.* and com.sun.net.* reference JDK-internal APIs that are "
|
|
378
|
+
"not part of the public specification. Since Java 9 (JPMS), these packages are "
|
|
379
|
+
"strongly encapsulated and require --add-exports / --add-opens JVM flags, "
|
|
380
|
+
"which are cumbersome and may be removed in future Java releases."
|
|
381
|
+
),
|
|
382
|
+
fix_hint=(
|
|
383
|
+
"Replace internal API usage with public equivalents. "
|
|
384
|
+
"For com.sun.net.httpserver, migrate to java.net.http.HttpServer or a framework. "
|
|
385
|
+
"Add '--add-exports java.base/sun.misc=ALL-UNNAMED' only as a last resort."
|
|
386
|
+
),
|
|
387
|
+
migration_target="java_9_plus",
|
|
388
|
+
openrewrite_recipe=None,
|
|
389
|
+
import_pattern=re.compile(
|
|
390
|
+
r"^[ \t]*import\s+(sun\.[^;]+|com\.sun\.(?:net|tools|jdi|source|management)[^;]+);",
|
|
391
|
+
re.MULTILINE,
|
|
392
|
+
),
|
|
393
|
+
),
|
|
394
|
+
_Rule(
|
|
395
|
+
id="MIG-013",
|
|
396
|
+
severity="high",
|
|
397
|
+
title="sun.misc.Unsafe — direct access requires --add-opens since Java 9",
|
|
398
|
+
explanation=(
|
|
399
|
+
"sun.misc.Unsafe is a JDK-internal class not exposed by the public module system. "
|
|
400
|
+
"Accessing it via reflection or direct import requires "
|
|
401
|
+
"'--add-opens java.base/sun.misc=ALL-UNNAMED' on Java 9+. "
|
|
402
|
+
"Many frameworks (ByteBuddy, CGLIB, ASM) use Unsafe internally."
|
|
403
|
+
),
|
|
404
|
+
fix_hint=(
|
|
405
|
+
"Remove direct Unsafe usage and rely on VarHandle (java.lang.invoke.VarHandle) "
|
|
406
|
+
"as the public replacement. Ensure framework versions used are Java 17+ compatible."
|
|
407
|
+
),
|
|
408
|
+
migration_target="java_9_plus",
|
|
409
|
+
openrewrite_recipe=None,
|
|
410
|
+
import_pattern=re.compile(
|
|
411
|
+
r"^[ \t]*import\s+(sun\.misc\.Unsafe[^;]*);",
|
|
412
|
+
re.MULTILINE,
|
|
413
|
+
),
|
|
414
|
+
code_pattern=re.compile(
|
|
415
|
+
r'Unsafe\.getUnsafe\s*\(|"theUnsafe"|getDeclaredField\s*\(\s*["\']theUnsafe["\']',
|
|
416
|
+
),
|
|
417
|
+
),
|
|
418
|
+
_Rule(
|
|
419
|
+
id="MIG-014",
|
|
420
|
+
severity="medium",
|
|
421
|
+
title="setAccessible(true) — may throw InaccessibleObjectException on Java 9+",
|
|
422
|
+
explanation=(
|
|
423
|
+
"Reflective access via setAccessible(true) to JDK-internal classes throws "
|
|
424
|
+
"InaccessibleObjectException on Java 9+ unless the owning module grants access. "
|
|
425
|
+
"This is an 'illegal reflective access' warning in Java 9-15 and a hard failure "
|
|
426
|
+
"in Java 17+ for strongly-encapsulated modules."
|
|
427
|
+
),
|
|
428
|
+
fix_hint=(
|
|
429
|
+
"Ensure setAccessible() calls target application code, not JDK internal classes. "
|
|
430
|
+
"Add necessary '--add-opens' flags for unavoidable cases. "
|
|
431
|
+
"Prefer public APIs to avoid reflection on JDK internals entirely."
|
|
432
|
+
),
|
|
433
|
+
migration_target="java_9_plus",
|
|
434
|
+
openrewrite_recipe=None,
|
|
435
|
+
code_pattern=re.compile(r"\.setAccessible\s*\(\s*true\s*\)"),
|
|
436
|
+
),
|
|
437
|
+
_Rule(
|
|
438
|
+
id="MIG-025",
|
|
439
|
+
severity="medium",
|
|
440
|
+
title="ReflectionFactory / MethodHandles.privateLookupIn — deep-reflection JPMS risk",
|
|
441
|
+
explanation=(
|
|
442
|
+
"sun.reflect.ReflectionFactory bypasses module encapsulation and is not part of "
|
|
443
|
+
"the public API. MethodHandles.privateLookupIn() grants private lookup access that "
|
|
444
|
+
"requires --add-opens on Java 9+. Both patterns are common in serialization "
|
|
445
|
+
"frameworks and mocking libraries and may break under strict JPMS modules."
|
|
446
|
+
),
|
|
447
|
+
fix_hint=(
|
|
448
|
+
"Replace sun.reflect.ReflectionFactory with MethodHandles.lookup() or VarHandle. "
|
|
449
|
+
"For MethodHandles.privateLookupIn, ensure the calling module has been opened "
|
|
450
|
+
"via 'opens <package> to <module>' in module-info.java."
|
|
451
|
+
),
|
|
452
|
+
migration_target="java_9_plus",
|
|
453
|
+
openrewrite_recipe=None,
|
|
454
|
+
import_pattern=re.compile(
|
|
455
|
+
r"^[ \t]*import\s+(sun\.reflect\.ReflectionFactory[^;]*);",
|
|
456
|
+
re.MULTILINE,
|
|
457
|
+
),
|
|
458
|
+
code_pattern=re.compile(
|
|
459
|
+
r"\bReflectionFactory\s*\.\s*getReflectionFactory\s*\("
|
|
460
|
+
r"|\bMethodHandles\s*\.\s*privateLookupIn\s*\(",
|
|
461
|
+
),
|
|
462
|
+
),
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
# ---------------------------------------------------------------------------
|
|
466
|
+
# Java 18+ — finalize() deprecated for removal
|
|
467
|
+
# ---------------------------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
_JAVA_18_RULES: list[_Rule] = [
|
|
470
|
+
_Rule(
|
|
471
|
+
id="MIG-015",
|
|
472
|
+
severity="medium",
|
|
473
|
+
title="finalize() override — deprecated for removal since Java 9, removed in Java 18",
|
|
474
|
+
explanation=(
|
|
475
|
+
"Object.finalize() was deprecated in Java 9 and deprecated-for-removal in Java 18 "
|
|
476
|
+
"(JEP 421). Overriding finalize() is unreliable, may delay GC, and the mechanism "
|
|
477
|
+
"is being removed from the platform. Java 18+ emits warnings; future JDK versions "
|
|
478
|
+
"will not call finalizers."
|
|
479
|
+
),
|
|
480
|
+
fix_hint=(
|
|
481
|
+
"Replace finalize() with try-with-resources (AutoCloseable/Closeable) or "
|
|
482
|
+
"java.lang.ref.Cleaner for resource cleanup."
|
|
483
|
+
),
|
|
484
|
+
migration_target="java_18_plus",
|
|
485
|
+
openrewrite_recipe="org.openrewrite.java.migrate.RemoveFinalizeMethod",
|
|
486
|
+
code_pattern=re.compile(
|
|
487
|
+
r"\b(?:protected|public)\s+void\s+finalize\s*\(\s*\)",
|
|
488
|
+
),
|
|
489
|
+
),
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
# ---------------------------------------------------------------------------
|
|
493
|
+
# Best-practice / low-severity — legacy date/time API
|
|
494
|
+
# ---------------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
_LEGACY_API_RULES: list[_Rule] = [
|
|
497
|
+
_Rule(
|
|
498
|
+
id="MIG-016",
|
|
499
|
+
severity="low",
|
|
500
|
+
title="Legacy date/time API (java.util.Date / Calendar / SimpleDateFormat)",
|
|
501
|
+
explanation=(
|
|
502
|
+
"java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat are "
|
|
503
|
+
"thread-unsafe and error-prone. They are superseded by java.time (JSR-310) "
|
|
504
|
+
"introduced in Java 8. While not removed, they cause issues in multi-threaded "
|
|
505
|
+
"Spring applications and should be migrated before upgrading."
|
|
506
|
+
),
|
|
507
|
+
fix_hint=(
|
|
508
|
+
"Replace Date with LocalDate/LocalDateTime/ZonedDateTime, "
|
|
509
|
+
"Calendar with java.time.Calendar, "
|
|
510
|
+
"SimpleDateFormat with DateTimeFormatter (thread-safe)."
|
|
511
|
+
),
|
|
512
|
+
migration_target="java_8_best_practice",
|
|
513
|
+
openrewrite_recipe="org.openrewrite.java.migrate.JavaTimeAPIs",
|
|
514
|
+
import_pattern=re.compile(
|
|
515
|
+
r"^[ \t]*import\s+(java\.util\.(?:Date|Calendar|GregorianCalendar)"
|
|
516
|
+
r"|java\.text\.(?:SimpleDateFormat|DateFormat))[^;]*;",
|
|
517
|
+
re.MULTILINE,
|
|
518
|
+
),
|
|
519
|
+
),
|
|
131
520
|
]
|
|
132
521
|
|
|
133
|
-
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
# All Java source rules
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
_ALL_RULES: list[_Rule] = (
|
|
527
|
+
_JAKARTA_RULES
|
|
528
|
+
+ _SPRING_SECURITY_RULES
|
|
529
|
+
+ _JAVA_11_RULES
|
|
530
|
+
+ _JAVA_15_RULES
|
|
531
|
+
+ _JAVA_17_RULES
|
|
532
|
+
+ _JAVA_9_RULES
|
|
533
|
+
+ _JAVA_18_RULES
|
|
534
|
+
+ _LEGACY_API_RULES
|
|
535
|
+
)
|
|
134
536
|
|
|
135
537
|
SEVERITY_ORDER: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
136
538
|
|
|
137
539
|
|
|
540
|
+
# ---------------------------------------------------------------------------
|
|
541
|
+
# XML config rules (applied to Spring XML config files)
|
|
542
|
+
# ---------------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
@dataclass(frozen=True)
|
|
545
|
+
class _XmlRule:
|
|
546
|
+
id: str
|
|
547
|
+
severity: str
|
|
548
|
+
title: str
|
|
549
|
+
explanation: str
|
|
550
|
+
fix_hint: str
|
|
551
|
+
migration_target: str
|
|
552
|
+
openrewrite_recipe: Optional[str] = None
|
|
553
|
+
pattern: Optional[re.Pattern] = None
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
_XML_RULES: list[_XmlRule] = [
|
|
557
|
+
_XmlRule(
|
|
558
|
+
id="MIG-030",
|
|
559
|
+
severity="high",
|
|
560
|
+
title="javax.* class reference in Spring XML config — namespace not migrated",
|
|
561
|
+
explanation=(
|
|
562
|
+
"Spring XML bean definitions using class='javax.*' reference the old Java EE "
|
|
563
|
+
"namespace. When the application migrates to Spring Boot 3 / Jakarta EE 9+, these "
|
|
564
|
+
"bean class names must be updated to use the jakarta.* namespace equivalents. "
|
|
565
|
+
"Typical occurrences: persistence providers, validators, transaction managers."
|
|
566
|
+
),
|
|
567
|
+
fix_hint=(
|
|
568
|
+
"Update class='javax.*' attributes in XML bean definitions to the corresponding "
|
|
569
|
+
"jakarta.* class names. Run OpenRewrite or grep for 'javax.' in all XML config files."
|
|
570
|
+
),
|
|
571
|
+
migration_target="jakarta",
|
|
572
|
+
openrewrite_recipe=None,
|
|
573
|
+
pattern=re.compile(
|
|
574
|
+
r'(?:class|type|value)\s*=\s*["\'][^"\']*\bjavax\.[a-zA-Z]',
|
|
575
|
+
re.MULTILINE,
|
|
576
|
+
),
|
|
577
|
+
),
|
|
578
|
+
_XmlRule(
|
|
579
|
+
id="MIG-031",
|
|
580
|
+
severity="high",
|
|
581
|
+
title="Spring Security XML — old-style <http auto-config> or versioned schema ≤5",
|
|
582
|
+
explanation=(
|
|
583
|
+
"XML-based Spring Security configuration using <http auto-config='true'> or "
|
|
584
|
+
"pointing to a spring-security-[3-5].x.xsd schema requires significant migration "
|
|
585
|
+
"for Spring Security 6 (Spring Boot 3). The auto-config shortcut and many XML "
|
|
586
|
+
"namespace attributes were changed or removed in Spring Security 6."
|
|
587
|
+
),
|
|
588
|
+
fix_hint=(
|
|
589
|
+
"Migrate XML security config to Java-based @Configuration with SecurityFilterChain "
|
|
590
|
+
"@Bean. See the Spring Security 6 XML migration guide. "
|
|
591
|
+
"Update schema references to spring-security.xsd (no version) or use Spring Security 6 schemas."
|
|
592
|
+
),
|
|
593
|
+
migration_target="spring_security_6",
|
|
594
|
+
openrewrite_recipe="org.openrewrite.java.spring.security6.WebSecurityConfigurerAdapterToSecurityFilterChain",
|
|
595
|
+
pattern=re.compile(
|
|
596
|
+
r"<(?:\w+:)?http\s[^>]*auto-config\s*=\s*[\"']true[\"']"
|
|
597
|
+
r"|spring-security-[2345]\.\d+\.xsd",
|
|
598
|
+
re.IGNORECASE | re.MULTILINE,
|
|
599
|
+
),
|
|
600
|
+
),
|
|
601
|
+
_XmlRule(
|
|
602
|
+
id="MIG-032",
|
|
603
|
+
severity="high",
|
|
604
|
+
title="web.xml with Servlet ≤4 namespace — javax.servlet, must migrate to jakarta",
|
|
605
|
+
explanation=(
|
|
606
|
+
"A web.xml using the Java EE namespace (java.sun.com/xml/ns/javaee or "
|
|
607
|
+
"xmlns.jcp.org/xml/ns/javaee) declares a Servlet 2.x/3.x/4.x deployment descriptor. "
|
|
608
|
+
"These namespaces map to javax.servlet. Spring Boot 3 requires Jakarta Servlet 5.0+ "
|
|
609
|
+
"(namespace: jakarta.ee/xml/ns/jakartaee). The deployment descriptor must be updated."
|
|
610
|
+
),
|
|
611
|
+
fix_hint=(
|
|
612
|
+
"Update web.xml namespace from 'http://xmlns.jcp.org/xml/ns/javaee' to "
|
|
613
|
+
"'https://jakarta.ee/xml/ns/jakartaee' and set version='5.0' or '6.0'. "
|
|
614
|
+
"Update all filter-class and servlet-class entries from javax.* to jakarta.* equivalents."
|
|
615
|
+
),
|
|
616
|
+
migration_target="jakarta",
|
|
617
|
+
openrewrite_recipe=None,
|
|
618
|
+
pattern=re.compile(
|
|
619
|
+
r'xmlns\s*=\s*["\']https?://(?:java\.sun\.com|xmlns\.jcp\.org)/xml/ns/javaee["\']',
|
|
620
|
+
re.IGNORECASE | re.MULTILINE,
|
|
621
|
+
),
|
|
622
|
+
),
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
# XML files to scan: name-based heuristic (avoids scanning unrelated XML like Maven reports)
|
|
626
|
+
_XML_FILE_GLOBS: tuple[str, ...] = (
|
|
627
|
+
"web.xml",
|
|
628
|
+
"applicationContext.xml",
|
|
629
|
+
"applicationContext-*.xml",
|
|
630
|
+
"*applicationContext*.xml",
|
|
631
|
+
"*-context.xml",
|
|
632
|
+
"*Context.xml",
|
|
633
|
+
"*-config.xml",
|
|
634
|
+
"*Config.xml",
|
|
635
|
+
"*security*.xml",
|
|
636
|
+
"*Security*.xml",
|
|
637
|
+
"*servlet*.xml",
|
|
638
|
+
"*Servlet*.xml",
|
|
639
|
+
"beans.xml",
|
|
640
|
+
"*-beans.xml",
|
|
641
|
+
"*spring*.xml",
|
|
642
|
+
"*Spring*.xml",
|
|
643
|
+
"*dispatcher*.xml",
|
|
644
|
+
"*Dispatcher*.xml",
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
_SKIP_DIRS: frozenset[str] = frozenset([
|
|
648
|
+
"target", "build", ".git", ".gradle", ".mvn",
|
|
649
|
+
"node_modules", "__pycache__", ".idea", ".vscode",
|
|
650
|
+
"out", "dist", "bin", "generated-sources",
|
|
651
|
+
])
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _is_spring_xml_candidate(fname: str) -> bool:
|
|
655
|
+
return any(fnmatch.fnmatch(fname, g) for g in _XML_FILE_GLOBS)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _find_xml_config_files(root: Path) -> list[tuple[Path, str]]:
|
|
659
|
+
"""Compatibility shim — calls the combined scanner."""
|
|
660
|
+
xml_files, _ = _find_non_java_files(root)
|
|
661
|
+
return xml_files
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _find_build_files(root: Path) -> list[tuple[Path, str]]:
|
|
665
|
+
"""Compatibility shim — calls the combined scanner."""
|
|
666
|
+
_, build_files = _find_non_java_files(root)
|
|
667
|
+
return build_files
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _find_non_java_files(
|
|
671
|
+
root: Path,
|
|
672
|
+
) -> tuple[list[tuple[Path, str]], list[tuple[Path, str]]]:
|
|
673
|
+
"""Single os.walk returning (xml_config_files, build_files), excluding build dirs."""
|
|
674
|
+
xml_files: list[tuple[Path, str]] = []
|
|
675
|
+
build_files: list[tuple[Path, str]] = []
|
|
676
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
677
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
|
|
678
|
+
dp = Path(dirpath)
|
|
679
|
+
try:
|
|
680
|
+
rel_dir = dp.relative_to(root)
|
|
681
|
+
except ValueError:
|
|
682
|
+
continue
|
|
683
|
+
rel_prefix = str(rel_dir) if str(rel_dir) != "." else ""
|
|
684
|
+
for fname in filenames:
|
|
685
|
+
rel = f"{rel_prefix}/{fname}" if rel_prefix else fname
|
|
686
|
+
abs_path = dp / fname
|
|
687
|
+
if fname.endswith(".xml"):
|
|
688
|
+
if fname == "pom.xml":
|
|
689
|
+
build_files.append((abs_path, rel))
|
|
690
|
+
elif _is_spring_xml_candidate(fname):
|
|
691
|
+
xml_files.append((abs_path, rel))
|
|
692
|
+
elif fname in ("build.gradle", "build.gradle.kts"):
|
|
693
|
+
build_files.append((abs_path, rel))
|
|
694
|
+
return xml_files, build_files
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _scan_xml_file(text: str, rel_path: str) -> list["MigrationFinding"]:
|
|
698
|
+
"""Apply XML rules to raw XML text. Returns one finding per matched rule."""
|
|
699
|
+
findings: list[MigrationFinding] = []
|
|
700
|
+
for rule in _XML_RULES:
|
|
701
|
+
if rule.pattern is None:
|
|
702
|
+
continue
|
|
703
|
+
matches = list(rule.pattern.finditer(text))
|
|
704
|
+
if not matches:
|
|
705
|
+
continue
|
|
706
|
+
first_line = text[: matches[0].start()].count("\n") + 1
|
|
707
|
+
snippets = [m.group(0)[:120].strip() for m in matches[:5]]
|
|
708
|
+
findings.append(
|
|
709
|
+
MigrationFinding(
|
|
710
|
+
id=MigrationFinding.make_id(rule.id, rel_path),
|
|
711
|
+
rule_id=rule.id,
|
|
712
|
+
severity=rule.severity,
|
|
713
|
+
title=rule.title,
|
|
714
|
+
source_file=rel_path,
|
|
715
|
+
first_line=first_line,
|
|
716
|
+
imports_found=snippets,
|
|
717
|
+
explanation=rule.explanation,
|
|
718
|
+
fix_hint=rule.fix_hint,
|
|
719
|
+
migration_target=rule.migration_target,
|
|
720
|
+
openrewrite_recipe=rule.openrewrite_recipe,
|
|
721
|
+
)
|
|
722
|
+
)
|
|
723
|
+
return findings
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
# ---------------------------------------------------------------------------
|
|
727
|
+
# Dependency rules (applied to pom.xml / build.gradle / build.gradle.kts)
|
|
728
|
+
# ---------------------------------------------------------------------------
|
|
729
|
+
|
|
730
|
+
@dataclass(frozen=True)
|
|
731
|
+
class _DepRule:
|
|
732
|
+
id: str
|
|
733
|
+
severity: str
|
|
734
|
+
title: str
|
|
735
|
+
explanation: str
|
|
736
|
+
fix_hint: str
|
|
737
|
+
migration_target: str
|
|
738
|
+
openrewrite_recipe: Optional[str] = None
|
|
739
|
+
# Patterns applied to raw build file text.
|
|
740
|
+
# Each is tried independently; first match wins.
|
|
741
|
+
maven_pattern: Optional[re.Pattern] = None
|
|
742
|
+
gradle_pattern: Optional[re.Pattern] = None
|
|
743
|
+
# Optional fast pre-check: skip expensive regex if this string is absent.
|
|
744
|
+
quick_filter: Optional[str] = None
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
_DEP_RULES: list[_DepRule] = [
|
|
748
|
+
_DepRule(
|
|
749
|
+
id="MIG-040",
|
|
750
|
+
severity="high",
|
|
751
|
+
title="SpringFox (io.springfox) — incompatible with Spring Boot 3 / Spring Framework 6",
|
|
752
|
+
explanation=(
|
|
753
|
+
"SpringFox relies on Spring MVC internal request mapping infrastructure that was "
|
|
754
|
+
"removed in Spring Framework 6. Applications declaring io.springfox:springfox-* "
|
|
755
|
+
"dependencies will fail to start after migration to Spring Boot 3, even if the "
|
|
756
|
+
"Java source code compiles cleanly."
|
|
757
|
+
),
|
|
758
|
+
fix_hint=(
|
|
759
|
+
"Replace springfox-swagger2 + springfox-swagger-ui with "
|
|
760
|
+
"springdoc-openapi-starter-webmvc-ui (OpenAPI 3). "
|
|
761
|
+
"Also remove @EnableSwagger2 and any SpringFox Docket configuration beans."
|
|
762
|
+
),
|
|
763
|
+
migration_target="spring_boot_3",
|
|
764
|
+
openrewrite_recipe=None,
|
|
765
|
+
maven_pattern=re.compile(r"\bio\.springfox\b", re.IGNORECASE),
|
|
766
|
+
gradle_pattern=re.compile(r"\bio\.springfox\b", re.IGNORECASE),
|
|
767
|
+
),
|
|
768
|
+
_DepRule(
|
|
769
|
+
id="MIG-041",
|
|
770
|
+
severity="high",
|
|
771
|
+
title="Hibernate 5.x explicitly pinned — Spring Boot 3 requires Hibernate 6",
|
|
772
|
+
explanation=(
|
|
773
|
+
"Spring Boot 3 ships with Hibernate 6.x as the JPA provider, which implements "
|
|
774
|
+
"Jakarta Persistence 3.0. An explicit <version>5.*</version> for hibernate-core "
|
|
775
|
+
"overrides the Spring Boot BOM and will cause runtime incompatibilities: Hibernate 5 "
|
|
776
|
+
"implements javax.persistence (not jakarta.persistence)."
|
|
777
|
+
),
|
|
778
|
+
fix_hint=(
|
|
779
|
+
"Remove the explicit Hibernate version override and let the Spring Boot 3 BOM "
|
|
780
|
+
"manage it (Hibernate 6.x). Review breaking API changes between Hibernate 5 and 6 "
|
|
781
|
+
"in the Hibernate 6 migration guide."
|
|
782
|
+
),
|
|
783
|
+
migration_target="jakarta",
|
|
784
|
+
openrewrite_recipe=None,
|
|
785
|
+
maven_pattern=re.compile(
|
|
786
|
+
r"<dependency>(?:(?!</dependency>).)*?hibernate-core(?![-\w])(?:(?!</dependency>).)*?"
|
|
787
|
+
r"<version>\s*5\.",
|
|
788
|
+
re.DOTALL | re.IGNORECASE,
|
|
789
|
+
),
|
|
790
|
+
gradle_pattern=re.compile(
|
|
791
|
+
r"""['"](org\.hibernate(?:\.orm)?):hibernate-core:5\.""",
|
|
792
|
+
re.IGNORECASE,
|
|
793
|
+
),
|
|
794
|
+
quick_filter="hibernate-core",
|
|
795
|
+
),
|
|
796
|
+
_DepRule(
|
|
797
|
+
id="MIG-042",
|
|
798
|
+
severity="medium",
|
|
799
|
+
title="ByteBuddy < 1.12.x — may not support Java 17+ strong encapsulation",
|
|
800
|
+
explanation=(
|
|
801
|
+
"ByteBuddy versions before 1.12 lack stable support for Java 17+ strong JPMS "
|
|
802
|
+
"encapsulation. Spring AOP, Mockito, and Hibernate proxies all depend on ByteBuddy "
|
|
803
|
+
"internally. If an application pins byte-buddy at 1.0–1.11.x, proxy creation "
|
|
804
|
+
"may fail with InaccessibleObjectException on Java 17+."
|
|
805
|
+
),
|
|
806
|
+
fix_hint=(
|
|
807
|
+
"Remove explicit ByteBuddy version overrides and let Spring Boot 3 BOM manage it "
|
|
808
|
+
"(ships with 1.14.x+). If you must pin it, use >= 1.12.18."
|
|
809
|
+
),
|
|
810
|
+
migration_target="java_17",
|
|
811
|
+
openrewrite_recipe=None,
|
|
812
|
+
maven_pattern=re.compile(
|
|
813
|
+
r"<dependency>(?:(?!</dependency>).)*?byte-buddy(?:(?!</dependency>).)*?"
|
|
814
|
+
r"<version>\s*1\.(?:[0-9]|1[01])\.",
|
|
815
|
+
re.DOTALL | re.IGNORECASE,
|
|
816
|
+
),
|
|
817
|
+
gradle_pattern=re.compile(
|
|
818
|
+
r"""['"](net\.bytebuddy):byte-buddy:1\.(?:[0-9]|1[01])\.""",
|
|
819
|
+
re.IGNORECASE,
|
|
820
|
+
),
|
|
821
|
+
quick_filter="byte-buddy",
|
|
822
|
+
),
|
|
823
|
+
_DepRule(
|
|
824
|
+
id="MIG-043",
|
|
825
|
+
severity="high",
|
|
826
|
+
title="EhCache 2.x — incompatible with Spring Boot 3 / JCache JSR-107 migration",
|
|
827
|
+
explanation=(
|
|
828
|
+
"EhCache 2.x (net.sf.ehcache) uses the old JSR-107 cache API and is not compatible "
|
|
829
|
+
"with the Spring Boot 3 cache abstraction. Spring Boot 3 requires EhCache 3.x "
|
|
830
|
+
"(org.ehcache) which implements JCache 1.1 and uses a different configuration format."
|
|
831
|
+
),
|
|
832
|
+
fix_hint=(
|
|
833
|
+
"Migrate from net.sf.ehcache:ehcache to org.ehcache:ehcache:3.x. "
|
|
834
|
+
"Update ehcache.xml configuration to the EhCache 3 XML format. "
|
|
835
|
+
"Add the 'org.ehcache:ehcache::jakarta' classifier for Jakarta EE compatibility."
|
|
836
|
+
),
|
|
837
|
+
migration_target="spring_boot_3",
|
|
838
|
+
openrewrite_recipe=None,
|
|
839
|
+
maven_pattern=re.compile(
|
|
840
|
+
r"<groupId>\s*net\.sf\.ehcache\s*</groupId>",
|
|
841
|
+
re.IGNORECASE,
|
|
842
|
+
),
|
|
843
|
+
gradle_pattern=re.compile(
|
|
844
|
+
r"""['"](net\.sf\.ehcache):[^'"]+""",
|
|
845
|
+
re.IGNORECASE,
|
|
846
|
+
),
|
|
847
|
+
),
|
|
848
|
+
]
|
|
849
|
+
|
|
850
|
+
_BUILD_FILE_NAMES: tuple[str, ...] = ("pom.xml", "build.gradle", "build.gradle.kts")
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def _find_build_files(root: Path) -> list[tuple[Path, str]]:
|
|
854
|
+
"""Return (abs_path, rel_path) for pom.xml / build.gradle files, excluding build dirs."""
|
|
855
|
+
results: list[tuple[Path, str]] = []
|
|
856
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
857
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
|
|
858
|
+
dp = Path(dirpath)
|
|
859
|
+
try:
|
|
860
|
+
rel_dir = dp.relative_to(root)
|
|
861
|
+
except ValueError:
|
|
862
|
+
continue
|
|
863
|
+
for fname in filenames:
|
|
864
|
+
if fname in _BUILD_FILE_NAMES:
|
|
865
|
+
abs_path = dp / fname
|
|
866
|
+
rel = str(rel_dir / fname) if str(rel_dir) != "." else fname
|
|
867
|
+
results.append((abs_path, rel))
|
|
868
|
+
return results
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _scan_dep_file(text: str, rel_path: str) -> list["MigrationFinding"]:
|
|
872
|
+
"""Apply dependency rules to a build file. Returns one finding per matched rule."""
|
|
873
|
+
is_gradle = rel_path.endswith((".gradle", ".gradle.kts"))
|
|
874
|
+
findings: list[MigrationFinding] = []
|
|
875
|
+
for rule in _DEP_RULES:
|
|
876
|
+
if rule.quick_filter is not None and rule.quick_filter not in text:
|
|
877
|
+
continue
|
|
878
|
+
pattern = rule.gradle_pattern if is_gradle else rule.maven_pattern
|
|
879
|
+
if pattern is None:
|
|
880
|
+
continue
|
|
881
|
+
m = pattern.search(text)
|
|
882
|
+
if m is None:
|
|
883
|
+
continue
|
|
884
|
+
first_line = text[: m.start()].count("\n") + 1
|
|
885
|
+
findings.append(
|
|
886
|
+
MigrationFinding(
|
|
887
|
+
id=MigrationFinding.make_id(rule.id, rel_path),
|
|
888
|
+
rule_id=rule.id,
|
|
889
|
+
severity=rule.severity,
|
|
890
|
+
title=rule.title,
|
|
891
|
+
source_file=rel_path,
|
|
892
|
+
first_line=first_line,
|
|
893
|
+
imports_found=[m.group(0)[:120].strip()],
|
|
894
|
+
explanation=rule.explanation,
|
|
895
|
+
fix_hint=rule.fix_hint,
|
|
896
|
+
migration_target=rule.migration_target,
|
|
897
|
+
openrewrite_recipe=rule.openrewrite_recipe,
|
|
898
|
+
)
|
|
899
|
+
)
|
|
900
|
+
return findings
|
|
901
|
+
|
|
902
|
+
|
|
138
903
|
# ---------------------------------------------------------------------------
|
|
139
904
|
# Finding
|
|
140
905
|
# ---------------------------------------------------------------------------
|
|
141
906
|
|
|
142
907
|
@dataclass
|
|
143
908
|
class MigrationFinding:
|
|
144
|
-
id: str
|
|
145
|
-
rule_id: str
|
|
146
|
-
severity: str
|
|
909
|
+
id: str
|
|
910
|
+
rule_id: str
|
|
911
|
+
severity: str
|
|
147
912
|
title: str
|
|
148
|
-
source_file: str
|
|
149
|
-
first_line: int
|
|
150
|
-
imports_found: list[str] = field(default_factory=list)
|
|
913
|
+
source_file: str
|
|
914
|
+
first_line: int
|
|
915
|
+
imports_found: list[str] = field(default_factory=list)
|
|
151
916
|
explanation: str = ""
|
|
152
917
|
fix_hint: str = ""
|
|
918
|
+
migration_target: str = ""
|
|
919
|
+
openrewrite_recipe: Optional[str] = None
|
|
153
920
|
|
|
154
921
|
@staticmethod
|
|
155
922
|
def make_id(rule_id: str, source_file: str) -> str:
|
|
@@ -166,9 +933,15 @@ class MigrationFinding:
|
|
|
166
933
|
"first_line": self.first_line,
|
|
167
934
|
"explanation": self.explanation,
|
|
168
935
|
"fix_hint": self.fix_hint,
|
|
936
|
+
"migration_target": self.migration_target,
|
|
937
|
+
"auto_fix_available": bool(self.openrewrite_recipe),
|
|
169
938
|
}
|
|
170
939
|
if self.imports_found:
|
|
171
940
|
d["imports_found"] = self.imports_found
|
|
941
|
+
if self.openrewrite_recipe:
|
|
942
|
+
d["openrewrite_recipe"] = self.openrewrite_recipe
|
|
943
|
+
else:
|
|
944
|
+
d["manual_migration"] = True
|
|
172
945
|
return d
|
|
173
946
|
|
|
174
947
|
|
|
@@ -178,14 +951,13 @@ class MigrationFinding:
|
|
|
178
951
|
|
|
179
952
|
@dataclass
|
|
180
953
|
class MigrationReport:
|
|
181
|
-
schema_version: str = "1.
|
|
954
|
+
schema_version: str = "1.2"
|
|
182
955
|
generated_at: str = ""
|
|
183
956
|
repo_id: str = ""
|
|
184
957
|
git_head: str = ""
|
|
185
958
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
blocking_count: int = 0 # critical + high finding count
|
|
959
|
+
readiness_score: int = 100
|
|
960
|
+
blocking_count: int = 0
|
|
189
961
|
estimated_effort_days: float = 0.0
|
|
190
962
|
spring_boot_2_detected: bool = False
|
|
191
963
|
|
|
@@ -200,17 +972,17 @@ class MigrationReport:
|
|
|
200
972
|
|
|
201
973
|
by_severity: dict[str, int] = {"critical": 0, "high": 0, "medium": 0, "low": 0}
|
|
202
974
|
by_rule: dict[str, int] = {}
|
|
975
|
+
by_target: dict[str, int] = {}
|
|
203
976
|
affected_files: set[str] = set()
|
|
204
977
|
|
|
205
978
|
for f in self.findings:
|
|
206
979
|
by_severity[f.severity] = by_severity.get(f.severity, 0) + 1
|
|
207
980
|
by_rule[f.rule_id] = by_rule.get(f.rule_id, 0) + 1
|
|
981
|
+
by_target[f.migration_target] = by_target.get(f.migration_target, 0) + 1
|
|
208
982
|
affected_files.add(f.source_file)
|
|
209
983
|
|
|
210
984
|
self.blocking_count = by_severity["critical"] + by_severity["high"]
|
|
211
985
|
|
|
212
|
-
# Score: deduct per affected-file/severity combination (not per finding, to avoid
|
|
213
|
-
# double-counting a file that imports 10 javax.persistence classes).
|
|
214
986
|
critical_files: set[str] = set()
|
|
215
987
|
high_files: set[str] = set()
|
|
216
988
|
medium_files: set[str] = set()
|
|
@@ -233,7 +1005,6 @@ class MigrationReport:
|
|
|
233
1005
|
)
|
|
234
1006
|
self.readiness_score = max(0, 100 - deduction)
|
|
235
1007
|
|
|
236
|
-
# Effort: sum per distinct affected file weighted by severity
|
|
237
1008
|
self.estimated_effort_days = round(
|
|
238
1009
|
len(critical_files) * 0.5
|
|
239
1010
|
+ len(high_files) * 0.25
|
|
@@ -247,6 +1018,7 @@ class MigrationReport:
|
|
|
247
1018
|
"affected_files": len(affected_files),
|
|
248
1019
|
"by_severity": by_severity,
|
|
249
1020
|
"by_rule": by_rule,
|
|
1021
|
+
"by_migration_target": by_target,
|
|
250
1022
|
}
|
|
251
1023
|
return self
|
|
252
1024
|
|
|
@@ -287,16 +1059,19 @@ class MigrationReport:
|
|
|
287
1059
|
for f in sorted(visible, key=lambda x: (SEVERITY_ORDER.get(x.severity, 3), x.source_file)):
|
|
288
1060
|
lines.append(
|
|
289
1061
|
f"{f.rule_id} [{f.severity.upper()}] {f.source_file}:{f.first_line}"
|
|
1062
|
+
f" [{f.migration_target}]"
|
|
290
1063
|
)
|
|
291
1064
|
lines.append(f" {f.title}")
|
|
292
1065
|
lines.append(f" Fix: {f.fix_hint}")
|
|
1066
|
+
if f.openrewrite_recipe:
|
|
1067
|
+
lines.append(f" OpenRewrite: {f.openrewrite_recipe}")
|
|
293
1068
|
lines.append("")
|
|
294
1069
|
|
|
295
1070
|
return "\n".join(lines)
|
|
296
1071
|
|
|
297
1072
|
|
|
298
1073
|
# ---------------------------------------------------------------------------
|
|
299
|
-
#
|
|
1074
|
+
# Java source scanner
|
|
300
1075
|
# ---------------------------------------------------------------------------
|
|
301
1076
|
|
|
302
1077
|
def _scan_file(
|
|
@@ -307,44 +1082,54 @@ def _scan_file(
|
|
|
307
1082
|
findings: list[MigrationFinding] = []
|
|
308
1083
|
|
|
309
1084
|
for rule in rules:
|
|
1085
|
+
matched_imports: list[str] = []
|
|
1086
|
+
import_first_line: Optional[int] = None
|
|
1087
|
+
code_first_line: Optional[int] = None
|
|
1088
|
+
code_snippets: list[str] = []
|
|
1089
|
+
|
|
310
1090
|
if rule.import_pattern is not None:
|
|
311
1091
|
matches = list(rule.import_pattern.finditer(source))
|
|
312
|
-
if
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
)
|
|
1092
|
+
if matches:
|
|
1093
|
+
import_first_line = source[: matches[0].start()].count("\n") + 1
|
|
1094
|
+
matched_imports = [m.group(1) for m in matches]
|
|
1095
|
+
|
|
1096
|
+
if rule.code_pattern is not None:
|
|
1097
|
+
m = rule.code_pattern.search(source)
|
|
1098
|
+
if m is not None:
|
|
1099
|
+
code_first_line = source[: m.start()].count("\n") + 1
|
|
1100
|
+
code_snippets = [m.group(0).strip()]
|
|
330
1101
|
|
|
331
|
-
|
|
1102
|
+
extends_first_line: Optional[int] = None
|
|
1103
|
+
if rule.extends_pattern is not None:
|
|
332
1104
|
m = rule.extends_pattern.search(source)
|
|
333
|
-
if m is None:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
1105
|
+
if m is not None:
|
|
1106
|
+
extends_first_line = source[: m.start()].count("\n") + 1
|
|
1107
|
+
|
|
1108
|
+
candidate_lines = [
|
|
1109
|
+
ln for ln in (import_first_line, code_first_line, extends_first_line)
|
|
1110
|
+
if ln is not None
|
|
1111
|
+
]
|
|
1112
|
+
if not candidate_lines:
|
|
1113
|
+
continue
|
|
1114
|
+
|
|
1115
|
+
first_line = min(candidate_lines)
|
|
1116
|
+
all_matches = matched_imports + code_snippets
|
|
1117
|
+
|
|
1118
|
+
findings.append(
|
|
1119
|
+
MigrationFinding(
|
|
1120
|
+
id=MigrationFinding.make_id(rule.id, rel_path),
|
|
1121
|
+
rule_id=rule.id,
|
|
1122
|
+
severity=rule.severity,
|
|
1123
|
+
title=rule.title,
|
|
1124
|
+
source_file=rel_path,
|
|
1125
|
+
first_line=first_line,
|
|
1126
|
+
imports_found=all_matches,
|
|
1127
|
+
explanation=rule.explanation,
|
|
1128
|
+
fix_hint=rule.fix_hint,
|
|
1129
|
+
migration_target=rule.migration_target,
|
|
1130
|
+
openrewrite_recipe=rule.openrewrite_recipe,
|
|
347
1131
|
)
|
|
1132
|
+
)
|
|
348
1133
|
|
|
349
1134
|
return findings
|
|
350
1135
|
|
|
@@ -359,22 +1144,28 @@ def run_migrate_check(
|
|
|
359
1144
|
*,
|
|
360
1145
|
min_severity: str = "low",
|
|
361
1146
|
) -> MigrationReport:
|
|
362
|
-
"""Scan Java
|
|
1147
|
+
"""Scan a Java repository for migration blockers.
|
|
1148
|
+
|
|
1149
|
+
Scans:
|
|
1150
|
+
- Java source files (.java) against all 24 rules (MIG-001..MIG-025)
|
|
1151
|
+
- Spring XML config files (applicationContext.xml, web.xml, security XML, etc.)
|
|
1152
|
+
- Build descriptors (pom.xml, build.gradle) for incompatible dependencies
|
|
363
1153
|
|
|
364
1154
|
Args:
|
|
365
1155
|
file_paths: Relative Java file paths (from find_java_files).
|
|
366
1156
|
root: Absolute repo root.
|
|
367
|
-
min_severity: Filter threshold
|
|
368
|
-
from the report. Choices: critical | high | medium | low.
|
|
1157
|
+
min_severity: Filter threshold. Choices: critical | high | medium | low.
|
|
369
1158
|
|
|
370
1159
|
Returns:
|
|
371
|
-
MigrationReport with findings, readiness_score,
|
|
1160
|
+
MigrationReport with findings, readiness_score, effort estimate, and
|
|
1161
|
+
migration_target breakdown.
|
|
372
1162
|
"""
|
|
373
1163
|
min_order = SEVERITY_ORDER.get(min_severity, 3)
|
|
374
1164
|
all_findings: list[MigrationFinding] = []
|
|
375
1165
|
limitations: list[str] = []
|
|
376
1166
|
read_errors = 0
|
|
377
1167
|
|
|
1168
|
+
# ── Java source scan ────────────────────────────────────────────────────
|
|
378
1169
|
for rel_path in file_paths:
|
|
379
1170
|
abs_path = root / rel_path
|
|
380
1171
|
try:
|
|
@@ -384,14 +1175,44 @@ def run_migrate_check(
|
|
|
384
1175
|
continue
|
|
385
1176
|
|
|
386
1177
|
file_findings = _scan_file(source, rel_path, _ALL_RULES)
|
|
387
|
-
# Apply min_severity filter
|
|
388
1178
|
filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
389
1179
|
all_findings.extend(filtered)
|
|
390
1180
|
|
|
391
1181
|
if read_errors:
|
|
392
1182
|
limitations.append(f"{read_errors} file(s) could not be read and were skipped.")
|
|
393
1183
|
|
|
394
|
-
#
|
|
1184
|
+
# ── XML + dependency scan (single tree walk) ─────────────────────────────
|
|
1185
|
+
xml_files, build_files = _find_non_java_files(root)
|
|
1186
|
+
xml_read_errors = 0
|
|
1187
|
+
for abs_path, rel_path in xml_files:
|
|
1188
|
+
try:
|
|
1189
|
+
text = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
1190
|
+
except OSError:
|
|
1191
|
+
xml_read_errors += 1
|
|
1192
|
+
continue
|
|
1193
|
+
xml_findings = _scan_xml_file(text, rel_path)
|
|
1194
|
+
filtered = [f for f in xml_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
1195
|
+
all_findings.extend(filtered)
|
|
1196
|
+
|
|
1197
|
+
if xml_read_errors:
|
|
1198
|
+
limitations.append(f"{xml_read_errors} XML file(s) could not be read and were skipped.")
|
|
1199
|
+
|
|
1200
|
+
dep_read_errors = 0
|
|
1201
|
+
for abs_path, rel_path in build_files:
|
|
1202
|
+
try:
|
|
1203
|
+
text = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
1204
|
+
except OSError:
|
|
1205
|
+
dep_read_errors += 1
|
|
1206
|
+
continue
|
|
1207
|
+
dep_findings = _scan_dep_file(text, rel_path)
|
|
1208
|
+
filtered = [f for f in dep_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
1209
|
+
all_findings.extend(filtered)
|
|
1210
|
+
|
|
1211
|
+
if dep_read_errors:
|
|
1212
|
+
limitations.append(f"{dep_read_errors} build file(s) could not be read and were skipped.")
|
|
1213
|
+
|
|
1214
|
+
limitations.extend(_STATIC_LIMITATIONS)
|
|
1215
|
+
|
|
395
1216
|
spring_boot_2 = _detect_spring_boot_2(root)
|
|
396
1217
|
|
|
397
1218
|
report = MigrationReport(
|
|
@@ -400,12 +1221,15 @@ def run_migrate_check(
|
|
|
400
1221
|
limitations=limitations,
|
|
401
1222
|
metadata={
|
|
402
1223
|
"java_files_scanned": len(file_paths),
|
|
1224
|
+
"xml_files_scanned": len(xml_files),
|
|
1225
|
+
"build_files_scanned": len(build_files),
|
|
403
1226
|
"min_severity": min_severity,
|
|
404
1227
|
"rules_applied": [r.id for r in _ALL_RULES],
|
|
1228
|
+
"xml_rules_applied": [r.id for r in _XML_RULES],
|
|
1229
|
+
"dep_rules_applied": [r.id for r in _DEP_RULES],
|
|
405
1230
|
},
|
|
406
1231
|
)
|
|
407
1232
|
|
|
408
|
-
# Populate git_head — non-fatal
|
|
409
1233
|
try:
|
|
410
1234
|
import subprocess as _sub
|
|
411
1235
|
_r = _sub.run(
|
|
@@ -420,9 +1244,30 @@ def run_migrate_check(
|
|
|
420
1244
|
return report.finalize()
|
|
421
1245
|
|
|
422
1246
|
|
|
1247
|
+
# Remaining static limitations — things that truly require runtime analysis
|
|
1248
|
+
_STATIC_LIMITATIONS: list[str] = [
|
|
1249
|
+
"Thread.stop/suspend/resume detection is best-effort: variable type cannot be confirmed "
|
|
1250
|
+
"without compilation. Verify that flagged variables are typed as java.lang.Thread.",
|
|
1251
|
+
"JPMS --add-opens requirements: exact set of required flags cannot be determined without "
|
|
1252
|
+
"running the application against the target JDK.",
|
|
1253
|
+
"Transitive dependency compatibility: library versions resolved transitively (not declared "
|
|
1254
|
+
"directly) require 'mvn dependency:tree' or Gradle dependency insight for full analysis.",
|
|
1255
|
+
"Runtime proxy behaviour (CGLIB subclass proxies): compatibility with Java 17+ strong "
|
|
1256
|
+
"encapsulation depends on framework version at runtime, not import-level analysis.",
|
|
1257
|
+
"XML bean definitions referencing class names via property placeholders (${bean.class}) "
|
|
1258
|
+
"cannot be resolved statically.",
|
|
1259
|
+
]
|
|
1260
|
+
|
|
1261
|
+
|
|
423
1262
|
def _detect_spring_boot_2(root: Path) -> bool:
|
|
424
1263
|
"""Return True if any pom.xml or build.gradle declares spring-boot 2.x."""
|
|
425
|
-
_SB2 = re.compile(
|
|
1264
|
+
_SB2 = re.compile(
|
|
1265
|
+
r"(?:spring[.\-]boot[.\-]?(?:version|starter|parent)[^=\n]*[=:\s>\"']?\s*)"
|
|
1266
|
+
r"2\.\d+[\.\d]*|"
|
|
1267
|
+
r"<version>\s*2\.\d+[\.\d]*\s*</version>.*spring.boot|"
|
|
1268
|
+
r"spring.boot.*<version>\s*2\.\d+",
|
|
1269
|
+
re.IGNORECASE | re.DOTALL,
|
|
1270
|
+
)
|
|
426
1271
|
for name in ("pom.xml", "build.gradle", "build.gradle.kts"):
|
|
427
1272
|
candidate = root / name
|
|
428
1273
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.35.
|
|
3
|
+
Version: 1.35.25
|
|
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
|
-

|
|
44
44
|

|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -114,7 +114,7 @@ pipx install sourcecode
|
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
sourcecode version
|
|
117
|
-
# sourcecode 1.35.
|
|
117
|
+
# sourcecode 1.35.25
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=1fGjuVJyBU95TOEnJBXLcVgGYs_QYMvaKwPypDXVP9g,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
|
|
32
|
+
sourcecode/migrate_check.py,sha256=-jghKewJwMO0VCXML-ZY1KI_RQO5gd5-pyLCMg5u8jA,52971
|
|
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.
|
|
98
|
-
sourcecode-1.35.
|
|
99
|
-
sourcecode-1.35.
|
|
100
|
-
sourcecode-1.35.
|
|
101
|
-
sourcecode-1.35.
|
|
97
|
+
sourcecode-1.35.25.dist-info/METADATA,sha256=XdPhns9JN4tVct_5ARdKlbsb3w76e9rBFXZ_SI4-VC0,21297
|
|
98
|
+
sourcecode-1.35.25.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
99
|
+
sourcecode-1.35.25.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
100
|
+
sourcecode-1.35.25.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
101
|
+
sourcecode-1.35.25.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|