sourcecode 1.35.24__tar.gz → 1.35.25__tar.gz
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-1.35.24 → sourcecode-1.35.25}/PKG-INFO +3 -3
- {sourcecode-1.35.24 → sourcecode-1.35.25}/README.md +2 -2
- {sourcecode-1.35.24 → sourcecode-1.35.25}/pyproject.toml +1 -1
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/migrate_check.py +518 -47
- {sourcecode-1.35.24 → sourcecode-1.35.25}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/.gitignore +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/.ruff.toml +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/CHANGELOG.md +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/CONTRIBUTING.md +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/LICENSE +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/SECURITY.md +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/raw +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/cli.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/license.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.35.24 → sourcecode-1.35.25}/src/sourcecode/workspace.py +0 -0
|
@@ -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
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -76,7 +76,7 @@ pipx install sourcecode
|
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
sourcecode version
|
|
79
|
-
# sourcecode 1.35.
|
|
79
|
+
# sourcecode 1.35.25
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sourcecode"
|
|
7
|
-
version = "1.35.
|
|
7
|
+
version = "1.35.25"
|
|
8
8
|
description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""migrate_check.py — Java 8/Spring Boot 2 migration readiness checker.
|
|
2
2
|
|
|
3
|
-
Scans Java source files
|
|
3
|
+
Scans Java source files, Spring XML config, and build descriptors for patterns
|
|
4
|
+
that must be addressed when migrating:
|
|
4
5
|
- Spring Boot 2 → 3 (javax → jakarta, Spring Security 6)
|
|
5
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)
|
|
6
9
|
|
|
7
10
|
Entry point: run_migrate_check(file_paths, root) → MigrationReport
|
|
8
11
|
"""
|
|
9
12
|
from __future__ import annotations
|
|
10
13
|
|
|
14
|
+
import fnmatch
|
|
11
15
|
import hashlib
|
|
16
|
+
import os
|
|
12
17
|
import re
|
|
13
18
|
from dataclasses import dataclass, field
|
|
14
19
|
from datetime import datetime, timezone
|
|
@@ -17,7 +22,7 @@ from typing import Optional
|
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
# ---------------------------------------------------------------------------
|
|
20
|
-
# Rule catalogue
|
|
25
|
+
# Rule catalogue — Java source rules
|
|
21
26
|
# ---------------------------------------------------------------------------
|
|
22
27
|
|
|
23
28
|
@dataclass(frozen=True)
|
|
@@ -27,11 +32,11 @@ class _Rule:
|
|
|
27
32
|
title: str
|
|
28
33
|
explanation: str
|
|
29
34
|
fix_hint: str
|
|
30
|
-
migration_target: str = "spring_boot_3"
|
|
31
|
-
openrewrite_recipe: Optional[str] = None
|
|
32
|
-
import_pattern: Optional[re.Pattern] = None
|
|
33
|
-
extends_pattern: Optional[re.Pattern] = None
|
|
34
|
-
code_pattern: Optional[re.Pattern] = None
|
|
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
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
# ---------------------------------------------------------------------------
|
|
@@ -215,7 +220,7 @@ _SPRING_SECURITY_RULES: list[_Rule] = [
|
|
|
215
220
|
]
|
|
216
221
|
|
|
217
222
|
# ---------------------------------------------------------------------------
|
|
218
|
-
# Java 11 — APIs removed from the JDK
|
|
223
|
+
# Java 11 — APIs removed from the JDK
|
|
219
224
|
# ---------------------------------------------------------------------------
|
|
220
225
|
|
|
221
226
|
_JAVA_11_RULES: list[_Rule] = [
|
|
@@ -252,6 +257,28 @@ _JAVA_11_RULES: list[_Rule] = [
|
|
|
252
257
|
openrewrite_recipe=None,
|
|
253
258
|
import_pattern=re.compile(r"^[ \t]*import\s+(javax\.xml\.ws[^;]+);", re.MULTILINE),
|
|
254
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
|
+
),
|
|
255
282
|
]
|
|
256
283
|
|
|
257
284
|
# ---------------------------------------------------------------------------
|
|
@@ -280,7 +307,7 @@ _JAVA_15_RULES: list[_Rule] = [
|
|
|
280
307
|
]
|
|
281
308
|
|
|
282
309
|
# ---------------------------------------------------------------------------
|
|
283
|
-
# Java 17 — SecurityManager removed (JEP 411)
|
|
310
|
+
# Java 17 — SecurityManager removed (JEP 411), Thread deprecated methods
|
|
284
311
|
# ---------------------------------------------------------------------------
|
|
285
312
|
|
|
286
313
|
_JAVA_17_RULES: list[_Rule] = [
|
|
@@ -304,12 +331,37 @@ _JAVA_17_RULES: list[_Rule] = [
|
|
|
304
331
|
openrewrite_recipe=None,
|
|
305
332
|
code_pattern=re.compile(
|
|
306
333
|
r"System\.(get|set)SecurityManager\s*\(|"
|
|
307
|
-
r"\bSecurityManager\s+\w+\s*[=;({]|"
|
|
334
|
+
r"\bSecurityManager\s+\w+\s*[=;({]|"
|
|
308
335
|
r"\bnew\s+SecurityManager\s*\(|"
|
|
309
336
|
r"\bextends\s+SecurityManager\b|"
|
|
310
337
|
r"\bAccessController\.(doPrivileged|checkPermission|getContext)\s*\(",
|
|
311
338
|
),
|
|
312
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
|
+
),
|
|
313
365
|
]
|
|
314
366
|
|
|
315
367
|
# ---------------------------------------------------------------------------
|
|
@@ -382,6 +434,32 @@ _JAVA_9_RULES: list[_Rule] = [
|
|
|
382
434
|
openrewrite_recipe=None,
|
|
383
435
|
code_pattern=re.compile(r"\.setAccessible\s*\(\s*true\s*\)"),
|
|
384
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
|
+
),
|
|
385
463
|
]
|
|
386
464
|
|
|
387
465
|
# ---------------------------------------------------------------------------
|
|
@@ -404,7 +482,7 @@ _JAVA_18_RULES: list[_Rule] = [
|
|
|
404
482
|
"java.lang.ref.Cleaner for resource cleanup."
|
|
405
483
|
),
|
|
406
484
|
migration_target="java_18_plus",
|
|
407
|
-
openrewrite_recipe=
|
|
485
|
+
openrewrite_recipe="org.openrewrite.java.migrate.RemoveFinalizeMethod",
|
|
408
486
|
code_pattern=re.compile(
|
|
409
487
|
r"\b(?:protected|public)\s+void\s+finalize\s*\(\s*\)",
|
|
410
488
|
),
|
|
@@ -442,7 +520,7 @@ _LEGACY_API_RULES: list[_Rule] = [
|
|
|
442
520
|
]
|
|
443
521
|
|
|
444
522
|
# ---------------------------------------------------------------------------
|
|
445
|
-
# All rules
|
|
523
|
+
# All Java source rules
|
|
446
524
|
# ---------------------------------------------------------------------------
|
|
447
525
|
|
|
448
526
|
_ALL_RULES: list[_Rule] = (
|
|
@@ -459,19 +537,382 @@ _ALL_RULES: list[_Rule] = (
|
|
|
459
537
|
SEVERITY_ORDER: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
460
538
|
|
|
461
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
|
+
|
|
462
903
|
# ---------------------------------------------------------------------------
|
|
463
904
|
# Finding
|
|
464
905
|
# ---------------------------------------------------------------------------
|
|
465
906
|
|
|
466
907
|
@dataclass
|
|
467
908
|
class MigrationFinding:
|
|
468
|
-
id: str
|
|
469
|
-
rule_id: str
|
|
470
|
-
severity: str
|
|
909
|
+
id: str
|
|
910
|
+
rule_id: str
|
|
911
|
+
severity: str
|
|
471
912
|
title: str
|
|
472
|
-
source_file: str
|
|
473
|
-
first_line: int
|
|
474
|
-
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)
|
|
475
916
|
explanation: str = ""
|
|
476
917
|
fix_hint: str = ""
|
|
477
918
|
migration_target: str = ""
|
|
@@ -493,11 +934,14 @@ class MigrationFinding:
|
|
|
493
934
|
"explanation": self.explanation,
|
|
494
935
|
"fix_hint": self.fix_hint,
|
|
495
936
|
"migration_target": self.migration_target,
|
|
937
|
+
"auto_fix_available": bool(self.openrewrite_recipe),
|
|
496
938
|
}
|
|
497
939
|
if self.imports_found:
|
|
498
940
|
d["imports_found"] = self.imports_found
|
|
499
941
|
if self.openrewrite_recipe:
|
|
500
942
|
d["openrewrite_recipe"] = self.openrewrite_recipe
|
|
943
|
+
else:
|
|
944
|
+
d["manual_migration"] = True
|
|
501
945
|
return d
|
|
502
946
|
|
|
503
947
|
|
|
@@ -507,14 +951,13 @@ class MigrationFinding:
|
|
|
507
951
|
|
|
508
952
|
@dataclass
|
|
509
953
|
class MigrationReport:
|
|
510
|
-
schema_version: str = "1.
|
|
954
|
+
schema_version: str = "1.2"
|
|
511
955
|
generated_at: str = ""
|
|
512
956
|
repo_id: str = ""
|
|
513
957
|
git_head: str = ""
|
|
514
958
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
blocking_count: int = 0 # critical + high finding count
|
|
959
|
+
readiness_score: int = 100
|
|
960
|
+
blocking_count: int = 0
|
|
518
961
|
estimated_effort_days: float = 0.0
|
|
519
962
|
spring_boot_2_detected: bool = False
|
|
520
963
|
|
|
@@ -540,8 +983,6 @@ class MigrationReport:
|
|
|
540
983
|
|
|
541
984
|
self.blocking_count = by_severity["critical"] + by_severity["high"]
|
|
542
985
|
|
|
543
|
-
# Score: deduct per affected-file/severity combination (not per finding, to avoid
|
|
544
|
-
# double-counting a file that imports 10 javax.persistence classes).
|
|
545
986
|
critical_files: set[str] = set()
|
|
546
987
|
high_files: set[str] = set()
|
|
547
988
|
medium_files: set[str] = set()
|
|
@@ -564,7 +1005,6 @@ class MigrationReport:
|
|
|
564
1005
|
)
|
|
565
1006
|
self.readiness_score = max(0, 100 - deduction)
|
|
566
1007
|
|
|
567
|
-
# Effort: sum per distinct affected file weighted by severity
|
|
568
1008
|
self.estimated_effort_days = round(
|
|
569
1009
|
len(critical_files) * 0.5
|
|
570
1010
|
+ len(high_files) * 0.25
|
|
@@ -631,7 +1071,7 @@ class MigrationReport:
|
|
|
631
1071
|
|
|
632
1072
|
|
|
633
1073
|
# ---------------------------------------------------------------------------
|
|
634
|
-
#
|
|
1074
|
+
# Java source scanner
|
|
635
1075
|
# ---------------------------------------------------------------------------
|
|
636
1076
|
|
|
637
1077
|
def _scan_file(
|
|
@@ -642,8 +1082,6 @@ def _scan_file(
|
|
|
642
1082
|
findings: list[MigrationFinding] = []
|
|
643
1083
|
|
|
644
1084
|
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
1085
|
matched_imports: list[str] = []
|
|
648
1086
|
import_first_line: Optional[int] = None
|
|
649
1087
|
code_first_line: Optional[int] = None
|
|
@@ -661,14 +1099,12 @@ def _scan_file(
|
|
|
661
1099
|
code_first_line = source[: m.start()].count("\n") + 1
|
|
662
1100
|
code_snippets = [m.group(0).strip()]
|
|
663
1101
|
|
|
664
|
-
# extends_pattern is a legacy form of code_pattern
|
|
665
1102
|
extends_first_line: Optional[int] = None
|
|
666
1103
|
if rule.extends_pattern is not None:
|
|
667
1104
|
m = rule.extends_pattern.search(source)
|
|
668
1105
|
if m is not None:
|
|
669
1106
|
extends_first_line = source[: m.start()].count("\n") + 1
|
|
670
1107
|
|
|
671
|
-
# Determine overall match
|
|
672
1108
|
candidate_lines = [
|
|
673
1109
|
ln for ln in (import_first_line, code_first_line, extends_first_line)
|
|
674
1110
|
if ln is not None
|
|
@@ -708,13 +1144,17 @@ def run_migrate_check(
|
|
|
708
1144
|
*,
|
|
709
1145
|
min_severity: str = "low",
|
|
710
1146
|
) -> MigrationReport:
|
|
711
|
-
"""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
|
|
712
1153
|
|
|
713
1154
|
Args:
|
|
714
1155
|
file_paths: Relative Java file paths (from find_java_files).
|
|
715
1156
|
root: Absolute repo root.
|
|
716
|
-
min_severity: Filter threshold
|
|
717
|
-
from the report. Choices: critical | high | medium | low.
|
|
1157
|
+
min_severity: Filter threshold. Choices: critical | high | medium | low.
|
|
718
1158
|
|
|
719
1159
|
Returns:
|
|
720
1160
|
MigrationReport with findings, readiness_score, effort estimate, and
|
|
@@ -725,6 +1165,7 @@ def run_migrate_check(
|
|
|
725
1165
|
limitations: list[str] = []
|
|
726
1166
|
read_errors = 0
|
|
727
1167
|
|
|
1168
|
+
# ── Java source scan ────────────────────────────────────────────────────
|
|
728
1169
|
for rel_path in file_paths:
|
|
729
1170
|
abs_path = root / rel_path
|
|
730
1171
|
try:
|
|
@@ -734,16 +1175,44 @@ def run_migrate_check(
|
|
|
734
1175
|
continue
|
|
735
1176
|
|
|
736
1177
|
file_findings = _scan_file(source, rel_path, _ALL_RULES)
|
|
737
|
-
# Apply min_severity filter
|
|
738
1178
|
filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
739
1179
|
all_findings.extend(filtered)
|
|
740
1180
|
|
|
741
1181
|
if read_errors:
|
|
742
1182
|
limitations.append(f"{read_errors} file(s) could not be read and were skipped.")
|
|
743
1183
|
|
|
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
|
+
|
|
744
1214
|
limitations.extend(_STATIC_LIMITATIONS)
|
|
745
1215
|
|
|
746
|
-
# Detect Spring Boot 2 pom.xml heuristic (best-effort, non-fatal)
|
|
747
1216
|
spring_boot_2 = _detect_spring_boot_2(root)
|
|
748
1217
|
|
|
749
1218
|
report = MigrationReport(
|
|
@@ -752,12 +1221,15 @@ def run_migrate_check(
|
|
|
752
1221
|
limitations=limitations,
|
|
753
1222
|
metadata={
|
|
754
1223
|
"java_files_scanned": len(file_paths),
|
|
1224
|
+
"xml_files_scanned": len(xml_files),
|
|
1225
|
+
"build_files_scanned": len(build_files),
|
|
755
1226
|
"min_severity": min_severity,
|
|
756
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],
|
|
757
1230
|
},
|
|
758
1231
|
)
|
|
759
1232
|
|
|
760
|
-
# Populate git_head — non-fatal
|
|
761
1233
|
try:
|
|
762
1234
|
import subprocess as _sub
|
|
763
1235
|
_r = _sub.run(
|
|
@@ -772,19 +1244,18 @@ def run_migrate_check(
|
|
|
772
1244
|
return report.finalize()
|
|
773
1245
|
|
|
774
1246
|
|
|
775
|
-
#
|
|
1247
|
+
# Remaining static limitations — things that truly require runtime analysis
|
|
776
1248
|
_STATIC_LIMITATIONS: list[str] = [
|
|
777
|
-
"Thread.stop/suspend/resume
|
|
778
|
-
"
|
|
779
|
-
"
|
|
780
|
-
"Module compatibility (JPMS): --add-opens requirements cannot be determined without "
|
|
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 "
|
|
781
1252
|
"running the application against the target JDK.",
|
|
782
|
-
"Transitive dependency compatibility: library versions
|
|
783
|
-
"
|
|
784
|
-
"
|
|
785
|
-
"
|
|
786
|
-
"
|
|
787
|
-
"
|
|
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.",
|
|
788
1259
|
]
|
|
789
1260
|
|
|
790
1261
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|