claudeos-core 2.5.0 → 2.5.2

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.
@@ -0,0 +1,562 @@
1
+ /**
2
+ * ClaudeOS-Core — JVM / Spring detection helpers
3
+ *
4
+ * Pure text functions. No filesystem access, no side effects: every function
5
+ * takes the build-file text (or a file list) and returns what that text
6
+ * literally declares. Nothing here invents a version — every returned
7
+ * version string is a substring of the input, or is resolved from a variable
8
+ * that is itself defined in the input.
9
+ *
10
+ * Why this exists (v2.5.x): stack-detector's Gradle branch only set
11
+ * `language = "java"` when it saw the string `spring-boot`. A legacy
12
+ * `apply plugin: 'java'` + `spring-webmvc:4.3.30.RELEASE` build reported
13
+ * `language: null` → "No language detected" → the Java scanner never ran.
14
+ * Maven set the language but never the framework or its version. Ant and
15
+ * Eclipse-WTP projects (build.xml, .classpath, WEB-INF/lib/*.jar — the
16
+ * shape of most Korean SI legacy) were invisible.
17
+ *
18
+ * Scope of "Spring Framework" here: group id EXACTLY `org.springframework`.
19
+ * `org.springframework.boot` / `.data` / `.security` / `.cloud` are other
20
+ * projects with their own version lines and are deliberately excluded from
21
+ * the framework-version search — a `spring-security-core:5.8.0` must never
22
+ * be reported as Spring Framework 5.8.0.
23
+ */
24
+
25
+ // ─── Version token ───────────────────────────────────────────────────
26
+ // Accepts `5.3.30`, `4.3.30.RELEASE`, `3.2.18.RELEASE`, `2.5.6`, `2.5.6.SEC03`,
27
+ // `6.1.0-M2`, `5.3.0-SNAPSHOT`. Must start with a digit.
28
+ const VER = "(\\d+(?:\\.\\d+){1,3}(?:[.-][A-Za-z0-9]+)*)";
29
+
30
+ // Spring Framework artifact names (group `org.springframework` only).
31
+ // `spring` alone is the 2.x-era single jar (`org.springframework:spring:2.5.6`).
32
+ // Spring 1.x published under the bare group `springframework`; 2.0+ under
33
+ // `org.springframework`. Both are the Framework; nothing else is.
34
+ const SPRING_GROUP = "(?:org\\.)?springframework";
35
+ const SPRING_FW_ARTIFACT = "spring(?:-(?:core|beans|context|context-support|context-indexer|aop|aspects|expression|instrument|jcl|jdbc|jms|messaging|orm|oxm|r2dbc|test|tx|web|webflux|webmvc|websocket|framework-bom|struts|ibatis|hibernate3|mock|agent|portlet|dao|support|remoting))?";
36
+
37
+ // ─── Gradle ──────────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Which JVM plugins does a Gradle build file apply?
41
+ * Handles the legacy `apply plugin: 'x'`, the `plugins { id 'x' }` DSL,
42
+ * the Kotlin-DSL `id("x")`, and Kotlin-DSL bare identifiers (`java`,
43
+ * `war`, `` `java-library` ``, `application`).
44
+ */
45
+ function gradleJvmPlugins(g) {
46
+ const found = new Set();
47
+ const add = (p) => { if (p) found.add(p); };
48
+ const PLUGINS = ["java", "java-library", "war", "ear", "application", "groovy", "org.springframework.boot", "spring-boot", "io.spring.dependency-management"];
49
+ for (const p of PLUGINS) {
50
+ const esc = p.replace(/[.-]/g, "\\$&");
51
+ if (new RegExp(`apply\\s+plugin:\\s*['"]${esc}['"]`).test(g)) add(p);
52
+ // Kotlin DSL legacy form: apply(plugin = "war")
53
+ if (new RegExp(`apply\\s*\\(\\s*plugin\\s*=\\s*['"]${esc}['"]\\s*\\)`).test(g)) add(p);
54
+ if (new RegExp(`\\bid\\s*\\(?\\s*['"]${esc}['"]`).test(g)) add(p);
55
+ }
56
+ // Kotlin DSL bare identifiers inside a plugins { } block only.
57
+ const pluginsBlock = g.match(/plugins\s*\{([\s\S]*?)\}/);
58
+ if (pluginsBlock) {
59
+ const body = pluginsBlock[1];
60
+ for (const line of body.split("\n")) {
61
+ const t = line.trim().replace(/\/\/.*$/, "").trim();
62
+ if (/^(java|war|ear|application|groovy)$/.test(t)) add(t);
63
+ if (/^`java-library`$/.test(t)) add("java-library");
64
+ }
65
+ }
66
+ return found;
67
+ }
68
+
69
+ function gradlePackaging(plugins) {
70
+ if (plugins.has("ear")) return "ear";
71
+ if (plugins.has("war")) return "war";
72
+ return null;
73
+ }
74
+
75
+ function gradleIsJvm(plugins) {
76
+ return ["java", "java-library", "war", "ear", "application", "groovy", "org.springframework.boot", "spring-boot"]
77
+ .some(p => plugins.has(p));
78
+ }
79
+
80
+ /**
81
+ * Does the Gradle build declare a Spring Framework dependency
82
+ * (group EXACTLY org.springframework)?
83
+ */
84
+ function gradleHasSpringFramework(g) {
85
+ // `org.springframework:spring-webmvc:…` / `org.springframework:spring:…`
86
+ // The lookahead forbids `org.springframework.boot:` etc. by requiring `:`
87
+ // immediately after the group.
88
+ if (new RegExp(`['"]${SPRING_GROUP}:${SPRING_FW_ARTIFACT}(?:[:'"]|$)`, "m").test(g)) return true;
89
+ // group: 'org.springframework', name: 'spring-webmvc' — any key order
90
+ if (gradleMapDeps(g).some(d => d.isSpringFw)) return true;
91
+ // mavenBom 'org.springframework:spring-framework-bom:…'
92
+ if (/spring-framework-bom/.test(g)) return true;
93
+ // version catalog accessor `libs.spring.webmvc` cannot be resolved here;
94
+ // the catalog itself is inspected by the caller.
95
+ return false;
96
+ }
97
+
98
+ /**
99
+ * Resolve `${name}` / `$name` / `name` against `ext { name = '…' }`,
100
+ * `def name = '…'`, `val name = "…"`, `name = "…"` inside the same file.
101
+ * Returns the literal or null.
102
+ */
103
+ function resolveGradleVar(g, name, props) {
104
+ // `${project.springVersion}`, `${rootProject.ext.springVersion}`, `${ext.x}`
105
+ // all denote the same ext property.
106
+ name = name.replace(/^(?:rootProject|project)\.(?:ext\.)?|^ext\./, "");
107
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
+ const m = g.match(new RegExp(`(?:^|[\\s{;])(?:val\\s+|def\\s+|ext\\.|project\\.ext\\.)?${esc}\\s*=\\s*['"]${VER}['"]`, "m"));
109
+ if (m) return m[1];
110
+ // Dotted access: `versions.spring` (Groovy map `versions = [spring: '…']`),
111
+ // `Versions.spring` (buildSrc `object Versions { const val spring = "…" }`),
112
+ // or a direct `versions.spring = '…'` assignment.
113
+ const dot = name.match(/^([\w]+)\.([\w]+)$/);
114
+ if (dot) {
115
+ const [, obj, key] = dot;
116
+ const eo = obj.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), ek = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
117
+ const map = g.match(new RegExp(`\\b${eo}\\s*=\\s*\\[([\\s\\S]*?)\\]`));
118
+ if (map) { const kv = map[1].match(new RegExp(`(?:^|[\\s,\\[])['"]?${ek}['"]?\\s*:\\s*['"]${VER}['"]`)); if (kv) return kv[1]; }
119
+ const objBlock = g.match(new RegExp(`object\\s+${eo}\\s*\\{([\\s\\S]*?)\\n\\}`));
120
+ if (objBlock) { const kv = objBlock[1].match(new RegExp(`\\bval\\s+${ek}\\s*(?::\\s*String)?\\s*=\\s*["']${VER}["']`)); if (kv) return kv[1]; }
121
+ const direct = g.match(new RegExp(`\\b${eo}\\.${ek}\\s*=\\s*['"]${VER}['"]`));
122
+ if (direct) return direct[1];
123
+ }
124
+ // Second source: gradle.properties (`springVersion=4.3.30.RELEASE`). Kotlin
125
+ // DSL `val springVersion: String by project` reads from there too.
126
+ if (props && typeof props[name] === "string" && new RegExp(`^${VER}$`).test(props[name].trim())) return props[name].trim();
127
+ return null;
128
+ }
129
+
130
+ /** Parse gradle.properties into {key: value}. Comments and blanks skipped. */
131
+ function parseGradleProperties(text) {
132
+ const out = {};
133
+ if (!text) return out;
134
+ for (const raw of text.split(/\r?\n/)) {
135
+ const line = raw.trim();
136
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
137
+ const m = line.match(/^([^=:\s]+)\s*[=:]\s*(.*)$/);
138
+ if (m) out[m[1]] = m[2];
139
+ }
140
+ return out;
141
+ }
142
+
143
+ /** Turn a captured version-or-variable into a literal, or null. */
144
+ function literalOrResolve(g, captured, props) {
145
+ if (!captured) return null;
146
+ const v = captured.match(/^\$\{?([\w.]+)\}?$/);
147
+ if (v) return resolveGradleVar(g, v[1], props);
148
+ return /^\d/.test(captured) ? captured : null;
149
+ }
150
+
151
+ /**
152
+ * Files a build script pulls in for variable definitions:
153
+ * `apply from: 'gradle/dependencies.gradle'` / `apply(from = "…")`.
154
+ * Returns the relative paths as written.
155
+ */
156
+ function gradleAppliedScripts(g) {
157
+ const out = [];
158
+ for (const m of g.matchAll(/apply\s*(?:\(\s*)?from\s*[:=]\s*['"]([^'"]+)['"]/g)) out.push(m[1]);
159
+ for (const m of g.matchAll(/apply\s+from\s*:\s*rootProject\.file\(\s*['"]([^'"]+)['"]\s*\)/g)) out.push(m[1]);
160
+ return [...new Set(out)];
161
+ }
162
+
163
+ /**
164
+ * Spring Framework version from a Gradle build file. Order:
165
+ * 1. spring-framework-bom coordinate (the one place a project pins it when
166
+ * individual deps are versionless)
167
+ * 2. explicit coordinate `org.springframework:spring-xxx:V`
168
+ * 3. group/name/version notation
169
+ * Each accepts a `${var}` and resolves it in-file.
170
+ */
171
+ function gradleSpringFrameworkVersion(g, props) {
172
+ const CANDIDATES = [
173
+ new RegExp(`spring-framework-bom:(\\$\\{?[\\w.]+\\}?|${VER})`),
174
+ new RegExp(`['"]${SPRING_GROUP}:${SPRING_FW_ARTIFACT}:(\\$\\{?[\\w.]+\\}?|${VER})['"]`),
175
+ ];
176
+ for (const re of CANDIDATES) {
177
+ const m = g.match(re);
178
+ if (!m) continue;
179
+ const v = literalOrResolve(g, m[1], props);
180
+ if (v) return v;
181
+ }
182
+ // group:/name:/version: map notation, any key order, single- or multi-line.
183
+ for (const d of gradleMapDeps(g)) {
184
+ if (!d.isSpringFw || !d.version) continue;
185
+ const v = literalOrResolve(g, d.version, props);
186
+ if (v) return v;
187
+ }
188
+ return null;
189
+ }
190
+
191
+ /**
192
+ * Parse Gradle map-notation dependencies: a run of `group:`/`name:`/`version:`
193
+ * pairs (2–3 of them, any order, commas/newlines between). Returns
194
+ * [{group, name, version, isSpringFw}].
195
+ */
196
+ function gradleMapDeps(g) {
197
+ const out = [];
198
+ const RUN = /((?:\b(?:group|name|version)\s*:\s*['"][^'"]*['"]\s*,?\s*){2,3})/g;
199
+ for (const m of g.matchAll(RUN)) {
200
+ const d = {};
201
+ for (const kv of m[1].matchAll(/\b(group|name|version)\s*:\s*['"]([^'"]*)['"]/g)) d[kv[1]] = kv[2];
202
+ if (!d.group || !d.name) continue;
203
+ d.isSpringFw = new RegExp(`^${SPRING_GROUP}$`).test(d.group) && new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(d.name);
204
+ out.push(d);
205
+ }
206
+ return out;
207
+ }
208
+
209
+ /**
210
+ * Spring Boot version from a Gradle build file — covers the forms the
211
+ * existing detector already handled PLUS the Boot 1.x/2.x buildscript
212
+ * classpath form (`spring-boot-gradle-plugin:1.5.22.RELEASE`, with or
213
+ * without a `${springBootVersion}` indirection).
214
+ */
215
+ function gradleSpringBootVersion(g, props) {
216
+ const CANDIDATES = [
217
+ new RegExp(`id\\s*\\(?\\s*['"]org\\.springframework\\.boot['"]\\s*\\)?\\s*version\\s*\\(?\\s*['"](\\$\\{?[\\w.]+\\}?|${VER})['"]`),
218
+ new RegExp(`spring-boot-gradle-plugin:(\\$\\{?[\\w.]+\\}?|${VER})`),
219
+ new RegExp(`spring-boot-dependencies:(\\$\\{?[\\w.]+\\}?|${VER})`),
220
+ new RegExp(`org\\.springframework\\.boot[^\\n]*version\\s*['"](\\$\\{?[\\w.]+\\}?|${VER})['"]`),
221
+ ];
222
+ for (const re of CANDIDATES) {
223
+ const m = g.match(re);
224
+ if (!m) continue;
225
+ const v = literalOrResolve(g, m[1], props);
226
+ if (v) return v;
227
+ }
228
+ return null;
229
+ }
230
+
231
+ // ─── Gradle version catalog (libs.versions.toml) ────────────────────
232
+
233
+ /**
234
+ * Spring Framework version from a version catalog. Finds a library whose
235
+ * module is `org.springframework:spring-*` and resolves its
236
+ * `version.ref` / inline `version`. Returns null if absent.
237
+ */
238
+ function catalogSpringFrameworkVersion(toml) {
239
+ if (!toml) return null;
240
+ const lib = toml.match(new RegExp(`module\\s*=\\s*["']org\\.springframework:${SPRING_FW_ARTIFACT}["'][^\\n]*`));
241
+ if (!lib) return null;
242
+ const line = lib[0];
243
+ const ref = line.match(/version\.ref\s*=\s*["']([\w.-]+)["']/);
244
+ if (ref) {
245
+ const def = toml.match(new RegExp(`^\\s*${ref[1].replace(/[.-]/g, "\\$&")}\\s*=\\s*["']${VER}["']`, "m"));
246
+ return def ? def[1] : null;
247
+ }
248
+ const inline = line.match(new RegExp(`version\\s*=\\s*["']${VER}["']`));
249
+ return inline ? inline[1] : null;
250
+ }
251
+
252
+ function catalogHasSpringFramework(toml) {
253
+ return !!toml && new RegExp(`org\\.springframework:${SPRING_FW_ARTIFACT}["']`).test(toml);
254
+ }
255
+
256
+ // ─── Maven ───────────────────────────────────────────────────────────
257
+
258
+ /** `<packaging>war</packaging>` at project level, or null when absent. */
259
+ function mavenPackaging(pom) {
260
+ const m = pom.match(/<packaging>\s*(jar|war|ear|pom|bundle)\s*<\/packaging>/);
261
+ return m ? m[1] : null;
262
+ }
263
+
264
+ /** Resolve `${prop}` against `<prop>value</prop>` in the same pom. */
265
+ function resolveMavenProp(pom, name) {
266
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
267
+ const m = pom.match(new RegExp(`<${esc}>\\s*${VER}\\s*</${esc}>`));
268
+ return m ? m[1] : null;
269
+ }
270
+
271
+ function literalOrResolveMaven(pom, captured) {
272
+ if (!captured) return null;
273
+ const v = captured.match(/^\$\{([\w.-]+)\}$/);
274
+ if (v) return resolveMavenProp(pom, v[1]);
275
+ return /^\d/.test(captured) ? captured : null;
276
+ }
277
+
278
+ /**
279
+ * Every <dependency> block whose groupId is EXACTLY org.springframework.
280
+ * Returns [{artifactId, version|null}]. Works on comment-stripped text.
281
+ */
282
+ function mavenSpringFrameworkDeps(pomClean) {
283
+ const out = [];
284
+ const blocks = pomClean.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g);
285
+ for (const b of blocks) {
286
+ const body = b[1];
287
+ if (!new RegExp(`<groupId>\\s*${SPRING_GROUP}\\s*</groupId>`).test(body)) continue;
288
+ const a = body.match(/<artifactId>\s*([\w.-]+)\s*<\/artifactId>/);
289
+ if (!a) continue;
290
+ if (!new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(a[1])) continue;
291
+ const v = body.match(/<version>\s*([^<]+?)\s*<\/version>/);
292
+ out.push({ artifactId: a[1], version: v ? v[1] : null });
293
+ }
294
+ return out;
295
+ }
296
+
297
+ function mavenHasSpringFramework(pomClean) {
298
+ return mavenSpringFrameworkDeps(pomClean).length > 0;
299
+ }
300
+
301
+ /**
302
+ * Spring Framework version from a pom. Order:
303
+ * 1. spring-framework-bom import (dependencyManagement)
304
+ * 2. first org.springframework dependency carrying a <version>
305
+ * 3. a conventional <spring.version> / <spring-framework.version> property
306
+ * `${prop}` values are resolved in-file.
307
+ */
308
+ function mavenSpringFrameworkVersion(pom, pomClean) {
309
+ const deps = mavenSpringFrameworkDeps(pomClean);
310
+ const bom = deps.find(d => d.artifactId === "spring-framework-bom" && d.version);
311
+ if (bom) { const v = literalOrResolveMaven(pom, bom.version); if (v) return v; }
312
+ for (const d of deps) {
313
+ if (!d.version) continue;
314
+ const v = literalOrResolveMaven(pom, d.version);
315
+ if (v) return v;
316
+ }
317
+ for (const prop of ["spring.version", "spring-framework.version", "springframework.version", "org.springframework.version"]) {
318
+ const v = resolveMavenProp(pom, prop);
319
+ if (v) return v;
320
+ }
321
+ return null;
322
+ }
323
+
324
+ /**
325
+ * Spring Boot version from a pom: starter-parent <version>, the
326
+ * spring-boot-dependencies BOM import, or a <spring-boot.version> property.
327
+ */
328
+ function mavenSpringBootVersion(pom, pomClean) {
329
+ // Bounded to the <parent> block: a `(?:(?!</parent>)[\s\S])*?` scan cannot
330
+ // run past </parent> into a later <dependency> that happens to mention
331
+ // spring-boot-starter-parent.
332
+ const parentBlock = pomClean.match(/<parent>((?:(?!<\/parent>)[\s\S])*)<\/parent>/);
333
+ if (parentBlock && /<artifactId>\s*spring-boot-starter-parent\s*<\/artifactId>/.test(parentBlock[1])) {
334
+ const pv = parentBlock[1].match(/<version>\s*([^<]+?)\s*<\/version>/);
335
+ if (pv) { const v = literalOrResolveMaven(pom, pv[1]); if (v) return v; }
336
+ }
337
+ const bom = pomClean.match(/<artifactId>\s*spring-boot-dependencies\s*<\/artifactId>\s*<version>\s*([^<]+?)\s*<\/version>/);
338
+ if (bom) { const v = literalOrResolveMaven(pom, bom[1]); if (v) return v; }
339
+ const prop = pom.match(new RegExp(`<spring-boot[.\\w-]*version>\\s*${VER}\\s*<`));
340
+ if (prop) return prop[1];
341
+ return null;
342
+ }
343
+
344
+ /**
345
+ * Java level from a legacy maven-compiler-plugin <configuration>
346
+ * (`<source>1.5</source>`), used when no <java.version> /
347
+ * <maven.compiler.source> property exists. Returns the raw token.
348
+ */
349
+ function mavenCompilerPluginSource(pom) {
350
+ // Walk <plugin> blocks individually so a compiler plugin WITHOUT a
351
+ // <configuration> cannot borrow <source> from the next plugin's block.
352
+ for (const b of pom.matchAll(/<plugin>((?:(?!<\/plugin>)[\s\S])*)<\/plugin>/g)) {
353
+ if (!/<artifactId>\s*maven-compiler-plugin\s*<\/artifactId>/.test(b[1])) continue;
354
+ const s = b[1].match(/<(?:source|release|target)>\s*(\d+(?:\.\d+)?)\s*<\/(?:source|release|target)>/);
355
+ return s ? s[1] : null;
356
+ }
357
+ return null;
358
+ }
359
+
360
+ // ─── Ant / Eclipse / no build tool ───────────────────────────────────
361
+
362
+ /** `<javac … source="1.6">` → "1.6". */
363
+ function antJavacSource(buildXml) {
364
+ const m = buildXml.match(/<javac\b[^>]*\b(?:source|target)\s*=\s*["'](\d+(?:\.\d+)?)["']/);
365
+ return m ? m[1] : null;
366
+ }
367
+
368
+ /** `.classpath` JRE container → "1.7" / "17"; null if absent. */
369
+ function eclipseJreLevel(classpathXml) {
370
+ const m = classpathXml.match(/(?:JavaSE|J2SE|JRE)-(\d+(?:\.\d+)?)/)
371
+ // Custom VM names: `jdk1.6.0_45`, `jre1.8.0_202`, `jdk-17.0.2`, `jdk17`
372
+ || classpathXml.match(/\/(?:jdk|jre)-?(1\.\d|\d{1,2})(?:[._]\d+)*["'/]/);
373
+ return m ? m[1] : null;
374
+ }
375
+
376
+ /** `.settings/org.eclipse.jdt.core.prefs` → compliance / source level. */
377
+ function eclipseJdtPrefsLevel(prefs) {
378
+ if (!prefs) return null;
379
+ const m = prefs.match(/org\.eclipse\.jdt\.core\.compiler\.(?:compliance|source)\s*=\s*(\d+(?:\.\d+)?)/);
380
+ return m ? m[1] : null;
381
+ }
382
+
383
+ /** Jar basenames referenced by `.classpath` `kind="lib"` / `kind="var"` entries. */
384
+ function eclipseClasspathJars(classpathXml) {
385
+ if (!classpathXml) return [];
386
+ const out = [];
387
+ for (const m of classpathXml.matchAll(/<classpathentry\b[^>]*\bkind\s*=\s*["'](?:lib|var)["'][^>]*\bpath\s*=\s*["']([^"']+\.jar)["']/gi)) out.push(m[1]);
388
+ for (const m of classpathXml.matchAll(/<classpathentry\b[^>]*\bpath\s*=\s*["']([^"']+\.jar)["'][^>]*\bkind\s*=\s*["'](?:lib|var)["']/gi)) out.push(m[1]);
389
+ return [...new Set(out)];
390
+ }
391
+
392
+ /** IntelliJ `.idea/misc.xml` → `languageLevel="JDK_1_7"` / `"JDK_17"`. */
393
+ function intellijLanguageLevel(miscXml) {
394
+ if (!miscXml) return null;
395
+ const m = miscXml.match(/languageLevel\s*=\s*["']JDK_(\d+)(?:_(\d+))?["']/);
396
+ if (!m) return null;
397
+ return m[2] ? `${m[1]}.${m[2]}` : m[1];
398
+ }
399
+
400
+ /** NetBeans `nbproject/project.properties` → { level, jars[] }. */
401
+ function netbeansProject(props) {
402
+ if (!props) return { level: null, jars: [] };
403
+ const level = (props.match(/^\s*javac\.(?:source|target)\s*=\s*(\d+(?:\.\d+)?)/m) || [])[1] || null;
404
+ const jars = [...props.matchAll(/^\s*file\.reference\.([^=\s]+\.jar)\s*=/gm)].map(m => m[1]);
405
+ return { level, jars };
406
+ }
407
+
408
+ /**
409
+ * `WEB-INF/web.xml` evidence: Spring MVC (`org.springframework.web.servlet.
410
+ * DispatcherServlet`, `ContextLoaderListener`), Struts, servlet spec version.
411
+ */
412
+ function webXmlFacts(webXml) {
413
+ if (!webXml) return { spring: false, struts: null, servletVersion: null };
414
+ return {
415
+ spring: /org\.springframework\./.test(webXml),
416
+ struts: /org\.apache\.struts2\.|StrutsPrepareAndExecuteFilter/.test(webXml) ? "struts2"
417
+ : /org\.apache\.struts\.action\.ActionServlet/.test(webXml) ? "struts" : null,
418
+ servletVersion: (webXml.match(/<web-app\b[^>]*\bversion\s*=\s*["'](\d+\.\d+)["']/) || [])[1] || null,
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Spring XSD schema versions in a Spring XML config —
424
+ * `…/spring-beans-3.0.xsd` → "3.0". Major.minor only; the lowest-fidelity
425
+ * version source, used only when nothing else pinned it. Returns the
426
+ * highest version seen (a 3.0 project may still reference a 2.5 schema for
427
+ * one namespace).
428
+ */
429
+ function springXsdVersion(xml) {
430
+ if (!xml) return null;
431
+ let best = null;
432
+ for (const m of xml.matchAll(/springframework\.org\/schema\/[\w-]+\/spring-[\w-]+-(\d+\.\d+)\.xsd/g)) {
433
+ if (!best || parseFloat(m[1]) > parseFloat(best)) best = m[1];
434
+ }
435
+ return best;
436
+ }
437
+
438
+ /** Struts / JSF tags from Maven or Gradle text: [{tag, version}] */
439
+ function legacyFrameworkTags(text) {
440
+ const tags = [];
441
+ const push = (tag, v) => { if (!tags.some(t => t.tag === tag)) tags.push({ tag, version: v || null }); };
442
+ // Maven blocks
443
+ for (const b of text.matchAll(/<dependency>((?:(?!<\/dependency>)[\s\S])*)<\/dependency>/g)) {
444
+ const g = (b[1].match(/<groupId>\s*([^<]+?)\s*<\/groupId>/) || [])[1];
445
+ const a = (b[1].match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/) || [])[1];
446
+ const v = (b[1].match(/<version>\s*([^<]+?)\s*<\/version>/) || [])[1];
447
+ if (!g || !a) continue;
448
+ if (/^(org\.apache\.)?struts$/.test(g) && /^(struts|struts-core)$/.test(a)) push("struts", v);
449
+ if (g === "org.apache.struts" && a === "struts2-core") push("struts2", v);
450
+ if (/^javax\.faces$|^jakarta\.faces$|^org\.glassfish$/.test(g) && /faces|jsf/.test(a)) push("jsf", v);
451
+ }
452
+ // Gradle coordinates
453
+ for (const m of text.matchAll(new RegExp(`['"]((?:org\\.apache\\.)?struts):(struts|struts-core|struts2-core):(${VER})['"]`, "g"))) push(m[2] === "struts2-core" ? "struts2" : "struts", m[3]);
454
+ for (const m of text.matchAll(new RegExp(`['"](?:javax|jakarta)\\.faces:[\\w.-]+:(${VER})['"]`, "g"))) push("jsf", m[1]);
455
+ return tags;
456
+ }
457
+
458
+ function eclipseHasJavaNature(projectXml) {
459
+ return /org\.eclipse\.jdt\.core\.javanature/.test(projectXml || "");
460
+ }
461
+
462
+ /**
463
+ * Classify a list of jar file names (basename only). Returns
464
+ * { springFramework: bool, springFrameworkVersion, springBoot: bool,
465
+ * springBootVersion }
466
+ * `spring-webmvc-3.0.5.RELEASE.jar` → 3.0.5.RELEASE; `spring.jar` →
467
+ * present, version null (Spring 2.0 era shipped unversioned names).
468
+ */
469
+ function classifyJars(jarNames) {
470
+ const r = { springFramework: false, springFrameworkVersion: null, springBoot: false, springBootVersion: null, databases: [], orm: null };
471
+ const fwRe = new RegExp(`^${SPRING_FW_ARTIFACT}(?:-${VER})?\\.jar$`);
472
+ const bootRe = new RegExp(`^spring-boot(?:-[\\w-]+)?-${VER}\\.jar$`);
473
+ // JDBC driver jar names → DB. Values reuse stack-detector's vocabulary.
474
+ // Includes drivers common in Korean enterprise deployments (Tibero, Altibase,
475
+ // Cubrid) — legacy SI systems frequently ship them in WEB-INF/lib.
476
+ const DB_JARS = [
477
+ [/^ojdbc\d*/i, "oracle"], [/^postgresql-/i, "postgresql"], [/^mysql-connector/i, "mysql"],
478
+ [/^mariadb-java-client/i, "mariadb"], [/^h2-/i, "h2"], [/^sqlite-jdbc/i, "sqlite"],
479
+ [/^(mssql-jdbc|sqljdbc|jtds)/i, "mssql"], [/^(db2jcc|jcc-)/i, "db2"], [/^tibero/i, "tibero"],
480
+ [/^altibase/i, "altibase"], [/^cubrid/i, "cubrid"], [/^mongo(db)?-(java-)?driver/i, "mongodb"],
481
+ ];
482
+ // ORM jar names. iBatis before MyBatis: `mybatis` is not a substring of
483
+ // `ibatis`, but keep explicit precedence for the same reason
484
+ // stack-detector's IBATIS_REGEX runs first.
485
+ const ORM_JARS = [
486
+ [/^ibatis/i, "ibatis"], [/^mybatis/i, "mybatis"], [/^hibernate/i, "jpa"],
487
+ [/^eclipselink/i, "jpa"], [/^openjpa/i, "jpa"], [/^jooq-/i, "jooq"],
488
+ ];
489
+ for (const n of jarNames) {
490
+ const b = n.match(bootRe);
491
+ if (b) { r.springBoot = true; if (!r.springBootVersion) r.springBootVersion = b[1]; continue; }
492
+ const f = n.match(fwRe);
493
+ if (f) { r.springFramework = true; if (!r.springFrameworkVersion && f[1]) r.springFrameworkVersion = f[1]; }
494
+ for (const [re, db] of DB_JARS) if (re.test(n) && !r.databases.includes(db)) r.databases.push(db);
495
+ if (!r.orm) for (const [re, orm] of ORM_JARS) if (re.test(n)) { r.orm = orm; break; }
496
+ }
497
+ return r;
498
+ }
499
+
500
+ // ─── Ant + Ivy ───────────────────────────────────────────────────────
501
+
502
+ /** `<dependency org="org.springframework" name="spring-webmvc" rev="V"/>` */
503
+ function ivySpringFrameworkVersion(ivy) {
504
+ if (!ivy) return null;
505
+ for (const m of ivy.matchAll(/<dependency\b([^>]*)\/?>/g)) {
506
+ const a = m[1];
507
+ if (!new RegExp(`\\borg\\s*=\\s*["']${SPRING_GROUP}["']`).test(a)) continue;
508
+ const name = (a.match(/\bname\s*=\s*["']([\w.-]+)["']/) || [])[1];
509
+ if (!name || !new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(name)) continue;
510
+ const rev = (a.match(new RegExp(`\\brev\\s*=\\s*["']${VER}["']`)) || [])[1];
511
+ if (rev) return rev;
512
+ }
513
+ return null;
514
+ }
515
+ function ivyHasSpringFramework(ivy) {
516
+ return !!ivy && [...ivy.matchAll(/<dependency\b([^>]*)\/?>/g)].some(m =>
517
+ new RegExp(`\\borg\\s*=\\s*["']${SPRING_GROUP}["']`).test(m[1]) &&
518
+ new RegExp(`\\bname\\s*=\\s*["']${SPRING_FW_ARTIFACT}["']`).test(m[1]));
519
+ }
520
+
521
+ // ─── eGovFrame (전자정부 표준프레임워크) ─────────────────────────────
522
+ // Korea's government-standard framework wraps Spring MVC. Group ids:
523
+ // `egovframework.rte` (RTE 2.x–4.x), `egovframework.rte.*`. Reported as
524
+ // `spring-framework` (templates and prompts understand Spring) plus an
525
+ // `egovframe <version>` tag in `detected`. Its poms pin Spring through
526
+ // `<spring.maven.version>`, which the Maven property list already resolves.
527
+
528
+ /** eGovFrame RTE version from a pom (comment-stripped) or a Gradle file. */
529
+ function egovframeVersion(text, propsText) {
530
+ if (!text) return null;
531
+ // Maven <dependency> with groupId egovframework.rte[.x]
532
+ for (const b of text.matchAll(/<dependency>((?:(?!<\/dependency>)[\s\S])*)<\/dependency>/g)) {
533
+ if (!/<groupId>\s*egovframework\.rte(?:\.[\w.]+)?\s*<\/groupId>/.test(b[1])) continue;
534
+ const v = (b[1].match(/<version>\s*([^<]+?)\s*<\/version>/) || [])[1];
535
+ if (!v) continue;
536
+ const lit = literalOrResolveMaven(propsText || text, v);
537
+ if (lit) return lit;
538
+ }
539
+ // Property fallback
540
+ const prop = resolveMavenProp(propsText || text, "egovframework.rte.version");
541
+ if (prop) return prop;
542
+ // Gradle coordinate
543
+ const g = text.match(new RegExp(`['"]egovframework\\.rte(?:\\.[\\w.]+)?:[\\w.-]+:(\\$\\{?[\\w.]+\\}?|${VER})['"]`));
544
+ if (g) return literalOrResolve(text, g[1]);
545
+ return null;
546
+ }
547
+ function hasEgovframe(text) {
548
+ return !!text && /egovframework\.rte/.test(text);
549
+ }
550
+
551
+ module.exports = {
552
+ SPRING_GROUP, gradleMapDeps, gradleAppliedScripts, eclipseJdtPrefsLevel, eclipseClasspathJars,
553
+ intellijLanguageLevel, netbeansProject, webXmlFacts, springXsdVersion, legacyFrameworkTags,
554
+ parseGradleProperties, ivySpringFrameworkVersion, ivyHasSpringFramework, egovframeVersion, hasEgovframe,
555
+ VER,
556
+ gradleJvmPlugins, gradlePackaging, gradleIsJvm, gradleHasSpringFramework,
557
+ gradleSpringFrameworkVersion, gradleSpringBootVersion, resolveGradleVar,
558
+ catalogSpringFrameworkVersion, catalogHasSpringFramework,
559
+ mavenPackaging, mavenSpringFrameworkDeps, mavenHasSpringFramework,
560
+ mavenSpringFrameworkVersion, mavenSpringBootVersion, mavenCompilerPluginSource,
561
+ antJavacSource, eclipseJreLevel, eclipseHasJavaNature, classifyJars,
562
+ };