gitnexus 1.4.7 → 1.4.9

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.
Files changed (242) hide show
  1. package/README.md +29 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index-repo.d.ts +15 -0
  7. package/dist/cli/index-repo.js +115 -0
  8. package/dist/cli/index.js +13 -3
  9. package/dist/cli/setup.js +90 -10
  10. package/dist/cli/wiki.d.ts +4 -0
  11. package/dist/cli/wiki.js +174 -53
  12. package/dist/config/supported-languages.d.ts +33 -1
  13. package/dist/config/supported-languages.js +32 -0
  14. package/dist/core/embeddings/embedder.d.ts +6 -1
  15. package/dist/core/embeddings/embedder.js +65 -5
  16. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  17. package/dist/core/embeddings/http-client.d.ts +31 -0
  18. package/dist/core/embeddings/http-client.js +179 -0
  19. package/dist/core/embeddings/index.d.ts +1 -0
  20. package/dist/core/embeddings/index.js +1 -0
  21. package/dist/core/embeddings/types.d.ts +1 -1
  22. package/dist/core/graph/graph.js +9 -1
  23. package/dist/core/graph/types.d.ts +11 -2
  24. package/dist/core/ingestion/call-processor.d.ts +66 -2
  25. package/dist/core/ingestion/call-processor.js +650 -30
  26. package/dist/core/ingestion/call-routing.d.ts +9 -18
  27. package/dist/core/ingestion/call-routing.js +0 -19
  28. package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
  29. package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
  30. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
  31. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
  32. package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
  33. package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
  34. package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
  35. package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
  36. package/dist/core/ingestion/cobol-processor.d.ts +54 -0
  37. package/dist/core/ingestion/cobol-processor.js +1186 -0
  38. package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
  39. package/dist/core/ingestion/entry-point-scoring.js +52 -28
  40. package/dist/core/ingestion/export-detection.d.ts +47 -8
  41. package/dist/core/ingestion/export-detection.js +29 -50
  42. package/dist/core/ingestion/field-extractor.d.ts +29 -0
  43. package/dist/core/ingestion/field-extractor.js +25 -0
  44. package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
  45. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
  46. package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
  48. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
  49. package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
  50. package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
  51. package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
  52. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
  53. package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
  54. package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
  55. package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
  56. package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
  57. package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
  58. package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
  59. package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
  60. package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
  61. package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
  62. package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
  63. package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
  64. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
  65. package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
  66. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
  67. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
  68. package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
  69. package/dist/core/ingestion/field-extractors/generic.js +111 -0
  70. package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
  71. package/dist/core/ingestion/field-extractors/typescript.js +291 -0
  72. package/dist/core/ingestion/field-types.d.ts +59 -0
  73. package/dist/core/ingestion/field-types.js +2 -0
  74. package/dist/core/ingestion/framework-detection.d.ts +97 -2
  75. package/dist/core/ingestion/framework-detection.js +114 -14
  76. package/dist/core/ingestion/heritage-processor.js +62 -66
  77. package/dist/core/ingestion/import-processor.d.ts +9 -10
  78. package/dist/core/ingestion/import-processor.js +150 -196
  79. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
  80. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
  81. package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
  82. package/dist/core/ingestion/import-resolvers/dart.js +44 -0
  83. package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
  84. package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
  85. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +10 -1
  86. package/dist/core/ingestion/import-resolvers/jvm.js +159 -0
  87. package/dist/core/ingestion/import-resolvers/php.d.ts +25 -0
  88. package/dist/core/ingestion/import-resolvers/php.js +80 -0
  89. package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
  90. package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
  91. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
  92. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
  93. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
  94. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
  95. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
  96. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
  97. package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
  98. package/dist/core/ingestion/import-resolvers/swift.js +23 -0
  99. package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
  100. package/dist/core/ingestion/import-resolvers/types.js +6 -0
  101. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +2 -0
  102. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +7 -0
  103. package/dist/core/ingestion/language-config.d.ts +6 -0
  104. package/dist/core/ingestion/language-config.js +13 -0
  105. package/dist/core/ingestion/language-provider.d.ts +121 -0
  106. package/dist/core/ingestion/language-provider.js +24 -0
  107. package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
  108. package/dist/core/ingestion/languages/c-cpp.js +71 -0
  109. package/dist/core/ingestion/languages/cobol.d.ts +1 -0
  110. package/dist/core/ingestion/languages/cobol.js +26 -0
  111. package/dist/core/ingestion/languages/csharp.d.ts +8 -0
  112. package/dist/core/ingestion/languages/csharp.js +49 -0
  113. package/dist/core/ingestion/languages/dart.d.ts +12 -0
  114. package/dist/core/ingestion/languages/dart.js +58 -0
  115. package/dist/core/ingestion/languages/go.d.ts +11 -0
  116. package/dist/core/ingestion/languages/go.js +28 -0
  117. package/dist/core/ingestion/languages/index.d.ts +38 -0
  118. package/dist/core/ingestion/languages/index.js +63 -0
  119. package/dist/core/ingestion/languages/java.d.ts +9 -0
  120. package/dist/core/ingestion/languages/java.js +29 -0
  121. package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
  122. package/dist/core/ingestion/languages/kotlin.js +53 -0
  123. package/dist/core/ingestion/languages/php.d.ts +8 -0
  124. package/dist/core/ingestion/languages/php.js +145 -0
  125. package/dist/core/ingestion/languages/python.d.ts +12 -0
  126. package/dist/core/ingestion/languages/python.js +39 -0
  127. package/dist/core/ingestion/languages/ruby.d.ts +9 -0
  128. package/dist/core/ingestion/languages/ruby.js +44 -0
  129. package/dist/core/ingestion/languages/rust.d.ts +12 -0
  130. package/dist/core/ingestion/languages/rust.js +44 -0
  131. package/dist/core/ingestion/languages/swift.d.ts +12 -0
  132. package/dist/core/ingestion/languages/swift.js +133 -0
  133. package/dist/core/ingestion/languages/typescript.d.ts +10 -0
  134. package/dist/core/ingestion/languages/typescript.js +60 -0
  135. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  136. package/dist/core/ingestion/markdown-processor.js +124 -0
  137. package/dist/core/ingestion/mro-processor.js +22 -18
  138. package/dist/core/ingestion/named-binding-processor.d.ts +18 -0
  139. package/dist/core/ingestion/named-binding-processor.js +42 -0
  140. package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
  141. package/dist/core/ingestion/named-bindings/csharp.js +37 -0
  142. package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
  143. package/dist/core/ingestion/named-bindings/java.js +29 -0
  144. package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
  145. package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
  146. package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
  147. package/dist/core/ingestion/named-bindings/php.js +61 -0
  148. package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
  149. package/dist/core/ingestion/named-bindings/python.js +49 -0
  150. package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
  151. package/dist/core/ingestion/named-bindings/rust.js +64 -0
  152. package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
  153. package/dist/core/ingestion/named-bindings/types.js +6 -0
  154. package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
  155. package/dist/core/ingestion/named-bindings/typescript.js +58 -0
  156. package/dist/core/ingestion/parsing-processor.d.ts +6 -2
  157. package/dist/core/ingestion/parsing-processor.js +125 -85
  158. package/dist/core/ingestion/pipeline.d.ts +10 -0
  159. package/dist/core/ingestion/pipeline.js +1235 -317
  160. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  161. package/dist/core/ingestion/resolution-context.js +8 -5
  162. package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
  163. package/dist/core/ingestion/route-extractors/expo.js +36 -0
  164. package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
  165. package/dist/core/ingestion/route-extractors/middleware.js +143 -0
  166. package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
  167. package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
  168. package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
  169. package/dist/core/ingestion/route-extractors/php.js +21 -0
  170. package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
  171. package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
  172. package/dist/core/ingestion/symbol-table.d.ts +16 -0
  173. package/dist/core/ingestion/symbol-table.js +20 -6
  174. package/dist/core/ingestion/tree-sitter-queries.d.ts +10 -9
  175. package/dist/core/ingestion/tree-sitter-queries.js +274 -11
  176. package/dist/core/ingestion/type-env.d.ts +42 -18
  177. package/dist/core/ingestion/type-env.js +481 -106
  178. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  179. package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
  180. package/dist/core/ingestion/type-extractors/csharp.js +149 -16
  181. package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
  182. package/dist/core/ingestion/type-extractors/dart.js +371 -0
  183. package/dist/core/ingestion/type-extractors/jvm.js +169 -66
  184. package/dist/core/ingestion/type-extractors/rust.js +35 -1
  185. package/dist/core/ingestion/type-extractors/shared.d.ts +1 -15
  186. package/dist/core/ingestion/type-extractors/shared.js +14 -112
  187. package/dist/core/ingestion/type-extractors/swift.js +338 -7
  188. package/dist/core/ingestion/type-extractors/types.d.ts +40 -8
  189. package/dist/core/ingestion/type-extractors/typescript.js +141 -9
  190. package/dist/core/ingestion/utils/ast-helpers.d.ts +83 -0
  191. package/dist/core/ingestion/utils/ast-helpers.js +817 -0
  192. package/dist/core/ingestion/utils/call-analysis.d.ts +73 -0
  193. package/dist/core/ingestion/utils/call-analysis.js +527 -0
  194. package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
  195. package/dist/core/ingestion/utils/event-loop.js +5 -0
  196. package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
  197. package/dist/core/ingestion/utils/language-detection.js +70 -0
  198. package/dist/core/ingestion/utils/verbose.d.ts +1 -0
  199. package/dist/core/ingestion/utils/verbose.js +7 -0
  200. package/dist/core/ingestion/workers/parse-worker.d.ts +55 -5
  201. package/dist/core/ingestion/workers/parse-worker.js +415 -225
  202. package/dist/core/lbug/csv-generator.js +51 -1
  203. package/dist/core/lbug/lbug-adapter.d.ts +10 -0
  204. package/dist/core/lbug/lbug-adapter.js +75 -4
  205. package/dist/core/lbug/schema.d.ts +8 -4
  206. package/dist/core/lbug/schema.js +65 -4
  207. package/dist/core/tree-sitter/parser-loader.js +7 -1
  208. package/dist/core/wiki/cursor-client.d.ts +31 -0
  209. package/dist/core/wiki/cursor-client.js +127 -0
  210. package/dist/core/wiki/generator.d.ts +28 -9
  211. package/dist/core/wiki/generator.js +115 -18
  212. package/dist/core/wiki/graph-queries.d.ts +4 -0
  213. package/dist/core/wiki/graph-queries.js +7 -1
  214. package/dist/core/wiki/llm-client.d.ts +2 -0
  215. package/dist/core/wiki/llm-client.js +8 -4
  216. package/dist/core/wiki/prompts.d.ts +3 -3
  217. package/dist/core/wiki/prompts.js +6 -0
  218. package/dist/mcp/core/embedder.js +11 -3
  219. package/dist/mcp/core/lbug-adapter.d.ts +5 -0
  220. package/dist/mcp/core/lbug-adapter.js +23 -2
  221. package/dist/mcp/local/local-backend.d.ts +38 -5
  222. package/dist/mcp/local/local-backend.js +804 -63
  223. package/dist/mcp/resources.js +2 -0
  224. package/dist/mcp/tools.js +73 -4
  225. package/dist/server/api.d.ts +19 -1
  226. package/dist/server/api.js +66 -6
  227. package/dist/storage/git.d.ts +12 -0
  228. package/dist/storage/git.js +21 -0
  229. package/dist/storage/repo-manager.d.ts +3 -0
  230. package/package.json +25 -16
  231. package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
  232. package/dist/core/ingestion/named-binding-extraction.js +0 -363
  233. package/dist/core/ingestion/resolvers/index.d.ts +0 -18
  234. package/dist/core/ingestion/resolvers/index.js +0 -13
  235. package/dist/core/ingestion/resolvers/jvm.js +0 -87
  236. package/dist/core/ingestion/resolvers/php.d.ts +0 -15
  237. package/dist/core/ingestion/resolvers/php.js +0 -35
  238. package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
  239. package/dist/core/ingestion/type-extractors/index.js +0 -31
  240. package/dist/core/ingestion/utils.d.ts +0 -138
  241. package/dist/core/ingestion/utils.js +0 -1290
  242. package/scripts/patch-tree-sitter-swift.cjs +0 -74
@@ -10,25 +10,16 @@
10
10
  * two packages have separate build targets (Node native vs WASM/browser).
11
11
  * Keep both copies in sync until a shared package is introduced.
12
12
  */
13
+ import type { SyntaxNode } from './utils/ast-helpers.js';
13
14
  /** null = this call was not routed; fall through to default call handling */
14
15
  export type CallRoutingResult = RubyCallRouting | null;
15
- export type CallRouter = (calledName: string, callNode: any) => CallRoutingResult;
16
- /** Per-language call routing. noRouting = no special routing (normal call processing) */
17
- export declare const callRouters: {
18
- javascript: CallRouter;
19
- typescript: CallRouter;
20
- python: CallRouter;
21
- java: CallRouter;
22
- kotlin: CallRouter;
23
- go: CallRouter;
24
- rust: CallRouter;
25
- csharp: CallRouter;
26
- php: CallRouter;
27
- swift: CallRouter;
28
- cpp: CallRouter;
29
- c: CallRouter;
30
- ruby: typeof routeRubyCall;
31
- };
16
+ /**
17
+ * Per-language call router.
18
+ * IMPORTANT: Call-routed imports bypass preprocessImportPath(), so any router that
19
+ * returns an importPath MUST validate it independently (length cap, control-char
20
+ * rejection). See routeRubyCall for the reference implementation.
21
+ */
22
+ export type CallRouter = (calledName: string, callNode: SyntaxNode) => CallRoutingResult;
32
23
  export type RubyCallRouting = {
33
24
  kind: 'import';
34
25
  importPath: string;
@@ -65,4 +56,4 @@ export interface RubyPropertyItem {
65
56
  * @param callNode - The tree-sitter `call` AST node
66
57
  * @returns A discriminated union describing the call's semantic role
67
58
  */
68
- export declare function routeRubyCall(calledName: string, callNode: any): RubyCallRouting;
59
+ export declare function routeRubyCall(calledName: string, callNode: SyntaxNode): RubyCallRouting;
@@ -10,25 +10,6 @@
10
10
  * two packages have separate build targets (Node native vs WASM/browser).
11
11
  * Keep both copies in sync until a shared package is introduced.
12
12
  */
13
- import { SupportedLanguages } from '../../config/supported-languages.js';
14
- /** No-op router: returns null for every call (passthrough to normal processing) */
15
- const noRouting = () => null;
16
- /** Per-language call routing. noRouting = no special routing (normal call processing) */
17
- export const callRouters = {
18
- [SupportedLanguages.JavaScript]: noRouting,
19
- [SupportedLanguages.TypeScript]: noRouting,
20
- [SupportedLanguages.Python]: noRouting,
21
- [SupportedLanguages.Java]: noRouting,
22
- [SupportedLanguages.Kotlin]: noRouting,
23
- [SupportedLanguages.Go]: noRouting,
24
- [SupportedLanguages.Rust]: noRouting,
25
- [SupportedLanguages.CSharp]: noRouting,
26
- [SupportedLanguages.PHP]: noRouting,
27
- [SupportedLanguages.Swift]: noRouting,
28
- [SupportedLanguages.CPlusPlus]: noRouting,
29
- [SupportedLanguages.C]: noRouting,
30
- [SupportedLanguages.Ruby]: routeRubyCall,
31
- };
32
13
  // ── Pre-allocated singletons for common return values ────────────────────────
33
14
  const CALL_RESULT = { kind: 'call' };
34
15
  const SKIP_RESULT = { kind: 'skip' };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * COBOL COPY statement expansion engine.
3
+ *
4
+ * Expands COPY statements by inlining copybook content, applying REPLACING
5
+ * transformations (LEADING, TRAILING, EXACT), and handling nested copies
6
+ * with cycle detection.
7
+ *
8
+ * This is a preprocessing step that runs BEFORE extractCobolSymbolsWithRegex.
9
+ * The caller should run preprocessCobolSource first to clean patch markers.
10
+ *
11
+ * Supported syntax:
12
+ * COPY CPSESP.
13
+ * COPY "WORKGRID.CPY".
14
+ * COPY CPSESP REPLACING LEADING "ESP-" BY "LK-ESP-"
15
+ * LEADING "KPSESPL" BY "LK-KPSESPL".
16
+ * COPY ANAZI REPLACING "ANAZI-KEY" BY "LK-KEY".
17
+ */
18
+ export interface CopyReplacing {
19
+ type: 'LEADING' | 'TRAILING' | 'EXACT';
20
+ from: string;
21
+ to: string;
22
+ isPseudotext?: boolean;
23
+ }
24
+ export interface CopyResolution {
25
+ copyTarget: string;
26
+ resolvedPath: string | null;
27
+ line: number;
28
+ replacing: CopyReplacing[];
29
+ library?: string;
30
+ }
31
+ export interface CopyExpansionResult {
32
+ expandedContent: string;
33
+ copyResolutions: CopyResolution[];
34
+ }
35
+ export declare const DEFAULT_MAX_DEPTH = 10;
36
+ /**
37
+ * Parse REPLACING clause text into structured replacements.
38
+ *
39
+ * Input examples:
40
+ * LEADING "ESP-" BY "LK-ESP-" LEADING "KPSESPL" BY "LK-KPSESPL"
41
+ * "ANAZI-KEY" BY "LK-KEY"
42
+ * TRAILING "-IN" BY "-OUT"
43
+ * ==CUST-== BY ==WS-CUST-==
44
+ * ==OLD-TEXT== BY ====
45
+ */
46
+ export declare function parseReplacingClause(text: string): CopyReplacing[];
47
+ /**
48
+ * Expand COBOL COPY statements by inlining copybook content.
49
+ *
50
+ * @param content - Source COBOL content (after preprocessCobolSource)
51
+ * @param filePath - Path of the source file (for diagnostics)
52
+ * @param resolveFile - Maps a COPY target name to a filesystem path, or null if not found
53
+ * @param readFile - Reads file content by path, or null if unreadable
54
+ * @param maxDepth - Maximum nesting depth for recursive expansion (default: 10)
55
+ * @returns Expanded content and resolution metadata
56
+ */
57
+ export declare function expandCopies(content: string, filePath: string, resolveFile: (name: string) => string | null, readFile: (path: string) => string | null, maxDepth?: number): CopyExpansionResult;
@@ -0,0 +1,385 @@
1
+ /**
2
+ * COBOL COPY statement expansion engine.
3
+ *
4
+ * Expands COPY statements by inlining copybook content, applying REPLACING
5
+ * transformations (LEADING, TRAILING, EXACT), and handling nested copies
6
+ * with cycle detection.
7
+ *
8
+ * This is a preprocessing step that runs BEFORE extractCobolSymbolsWithRegex.
9
+ * The caller should run preprocessCobolSource first to clean patch markers.
10
+ *
11
+ * Supported syntax:
12
+ * COPY CPSESP.
13
+ * COPY "WORKGRID.CPY".
14
+ * COPY CPSESP REPLACING LEADING "ESP-" BY "LK-ESP-"
15
+ * LEADING "KPSESPL" BY "LK-KPSESPL".
16
+ * COPY ANAZI REPLACING "ANAZI-KEY" BY "LK-KEY".
17
+ */
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+ export const DEFAULT_MAX_DEPTH = 10;
22
+ /** COBOL identifier pattern: starts with letter, contains letters, digits, hyphens. */
23
+ const RE_COBOL_IDENTIFIER = /\b([A-Z][A-Z0-9-]*)\b/gi;
24
+ // ---------------------------------------------------------------------------
25
+ // Private helpers
26
+ // ---------------------------------------------------------------------------
27
+ /**
28
+ * Strip inline comments (Italian-style `|` comments).
29
+ * Only strips if `|` appears in the code area (col 7+).
30
+ */
31
+ function stripInlineComment(line) {
32
+ let inQuote = null;
33
+ for (let i = 0; i < line.length; i++) {
34
+ const ch = line[i];
35
+ if (inQuote) {
36
+ if (ch === inQuote)
37
+ inQuote = null;
38
+ }
39
+ else if (ch === '"' || ch === "'") {
40
+ inQuote = ch;
41
+ }
42
+ else if (ch === '|') {
43
+ return line.substring(0, i);
44
+ }
45
+ }
46
+ return line;
47
+ }
48
+ /**
49
+ * Check if a line is a COBOL comment (indicator in col 7 is `*` or `/`).
50
+ */
51
+ function isCommentLine(line) {
52
+ return line.length >= 7 && (line[6] === '*' || line[6] === '/');
53
+ }
54
+ /**
55
+ * Check if a line is a continuation line (indicator in col 7 is `-`).
56
+ */
57
+ function isContinuationLine(line) {
58
+ return line.length >= 7 && line[6] === '-';
59
+ }
60
+ /**
61
+ * Merge continuation lines into their predecessors.
62
+ * Returns an array of logical lines with their original starting line numbers.
63
+ */
64
+ function mergeLogicalLines(rawLines) {
65
+ const logical = [];
66
+ for (let i = 0; i < rawLines.length; i++) {
67
+ const raw = rawLines[i];
68
+ // Skip comment lines
69
+ if (isCommentLine(raw)) {
70
+ logical.push({ text: '', lineNum: i + 1 });
71
+ continue;
72
+ }
73
+ // Continuation: merge into previous logical line
74
+ if (isContinuationLine(raw)) {
75
+ if (logical.length > 0) {
76
+ const prev = logical[logical.length - 1];
77
+ const continuation = raw.length > 7 ? raw.substring(7).trimStart() : '';
78
+ prev.text += continuation;
79
+ }
80
+ // Push empty placeholder to preserve line count
81
+ logical.push({ text: '', lineNum: i + 1 });
82
+ continue;
83
+ }
84
+ // Normal line: strip inline comments
85
+ const cleaned = stripInlineComment(raw);
86
+ logical.push({ text: cleaned, lineNum: i + 1 });
87
+ }
88
+ return logical;
89
+ }
90
+ /**
91
+ * Parse REPLACING clause text into structured replacements.
92
+ *
93
+ * Input examples:
94
+ * LEADING "ESP-" BY "LK-ESP-" LEADING "KPSESPL" BY "LK-KPSESPL"
95
+ * "ANAZI-KEY" BY "LK-KEY"
96
+ * TRAILING "-IN" BY "-OUT"
97
+ * ==CUST-== BY ==WS-CUST-==
98
+ * ==OLD-TEXT== BY ====
99
+ */
100
+ export function parseReplacingClause(text) {
101
+ const replacings = [];
102
+ if (!text || text.trim().length === 0)
103
+ return replacings;
104
+ const tokens = [];
105
+ const tokenRe = /==((?:[^=]|=[^=])*)==|"([^"]*)"|(\S+)/g;
106
+ let tm;
107
+ while ((tm = tokenRe.exec(text)) !== null) {
108
+ if (tm[1] !== undefined) {
109
+ // Pseudotext: trim leading/trailing whitespace
110
+ tokens.push({ value: tm[1].trim(), isPseudotext: true });
111
+ }
112
+ else if (tm[2] !== undefined) {
113
+ tokens.push({ value: tm[2], isPseudotext: false });
114
+ }
115
+ else {
116
+ tokens.push({ value: tm[3], isPseudotext: false });
117
+ }
118
+ }
119
+ // Parse token stream: [LEADING|TRAILING]? <from> BY <to>
120
+ let i = 0;
121
+ while (i < tokens.length) {
122
+ let type = 'EXACT';
123
+ // Check for type modifier (only on non-pseudotext tokens)
124
+ if (!tokens[i].isPseudotext) {
125
+ const upper = tokens[i].value.toUpperCase();
126
+ if (upper === 'LEADING') {
127
+ type = 'LEADING';
128
+ i++;
129
+ }
130
+ else if (upper === 'TRAILING') {
131
+ type = 'TRAILING';
132
+ i++;
133
+ }
134
+ }
135
+ if (i >= tokens.length)
136
+ break;
137
+ const fromToken = tokens[i];
138
+ i++;
139
+ // Pseudotext always forces EXACT type
140
+ if (fromToken.isPseudotext)
141
+ type = 'EXACT';
142
+ // Expect BY keyword
143
+ if (i >= tokens.length)
144
+ break;
145
+ if (tokens[i].value.toUpperCase() !== 'BY') {
146
+ // Malformed — skip this token and try to resync
147
+ continue;
148
+ }
149
+ i++; // skip BY
150
+ if (i >= tokens.length)
151
+ break;
152
+ const toToken = tokens[i];
153
+ i++;
154
+ replacings.push({ type, from: fromToken.value, to: toToken.value, isPseudotext: fromToken.isPseudotext || undefined });
155
+ }
156
+ return replacings;
157
+ }
158
+ /**
159
+ * Scan logical lines for COPY statements.
160
+ * COPY statements can span multiple lines and terminate with a period.
161
+ */
162
+ function parseCopyStatements(logicalLines) {
163
+ const results = [];
164
+ let accumulator = null;
165
+ let startLine = 0;
166
+ let endLine = 0;
167
+ for (let i = 0; i < logicalLines.length; i++) {
168
+ const { text, lineNum } = logicalLines[i];
169
+ if (text.length === 0)
170
+ continue;
171
+ // Check for COPY keyword start (not inside a string context)
172
+ const copyStart = text.match(/\bCOPY\b/i);
173
+ if (accumulator === null) {
174
+ if (!copyStart)
175
+ continue;
176
+ // Start accumulating from the COPY keyword onwards
177
+ const copyIdx = copyStart.index;
178
+ accumulator = text.substring(copyIdx);
179
+ startLine = lineNum;
180
+ endLine = lineNum;
181
+ }
182
+ else {
183
+ // Continue accumulating
184
+ accumulator += ' ' + text.trim();
185
+ endLine = lineNum;
186
+ }
187
+ // Check if statement terminates (period at end of accumulated text)
188
+ if (accumulator !== null && /\.\s*$/.test(accumulator)) {
189
+ const parsed = parseSingleCopyStatement(accumulator, startLine, endLine);
190
+ if (parsed) {
191
+ results.push(parsed);
192
+ }
193
+ accumulator = null;
194
+ }
195
+ }
196
+ // If there's an unterminated COPY (missing period), try to parse what we have
197
+ if (accumulator !== null) {
198
+ const parsed = parseSingleCopyStatement(accumulator, startLine, endLine);
199
+ if (parsed) {
200
+ results.push(parsed);
201
+ }
202
+ }
203
+ return results;
204
+ }
205
+ /**
206
+ * Parse a single complete COPY statement string.
207
+ *
208
+ * Formats:
209
+ * COPY target.
210
+ * COPY "target".
211
+ * COPY target REPLACING ... .
212
+ */
213
+ function parseSingleCopyStatement(stmt, startLine, endLine) {
214
+ // Strip terminating period
215
+ const text = stmt.replace(/\.\s*$/, '').trim();
216
+ // Extract target: COPY <target> or COPY "<target>" or COPY '<target>'
217
+ // Optionally followed by IN/OF <library-name> (COBOL-85 standard: IN and OF are synonyms)
218
+ const targetMatch = text.match(/^COPY\s+(?:"([^"]+)"|'([^']+)'|([A-Z][A-Z0-9-]*))(?:\s+(?:IN|OF)\s+([A-Z][A-Z0-9-]*))?/i);
219
+ if (!targetMatch)
220
+ return null;
221
+ const target = targetMatch[1] ?? targetMatch[2] ?? targetMatch[3];
222
+ const library = targetMatch[4] || undefined;
223
+ // Extract REPLACING clause if present
224
+ let replacing = [];
225
+ const replacingIdx = text.search(/\bREPLACING\b/i);
226
+ if (replacingIdx >= 0) {
227
+ const replacingText = text.substring(replacingIdx + 'REPLACING'.length);
228
+ replacing = parseReplacingClause(replacingText);
229
+ }
230
+ return { startLine, endLine, target, replacing, library };
231
+ }
232
+ // ---------------------------------------------------------------------------
233
+ // REPLACING application
234
+ // ---------------------------------------------------------------------------
235
+ /**
236
+ * Apply REPLACING transformations to copybook content.
237
+ *
238
+ * LEADING: replace prefix in COBOL identifiers.
239
+ * TRAILING: replace suffix in COBOL identifiers.
240
+ * EXACT: replace exact token matches.
241
+ */
242
+ function applyReplacing(content, replacings) {
243
+ if (replacings.length === 0)
244
+ return content;
245
+ // First pass: handle EXACT replacements that contain spaces or non-identifier
246
+ // characters (pseudotext). These cannot be handled by identifier-level matching.
247
+ let result = content;
248
+ for (const r of replacings) {
249
+ if (r.type === 'EXACT' && (r.isPseudotext || r.from.includes(' ') || !/^[A-Z][A-Z0-9-]*$/i.test(r.from))) {
250
+ const escaped = r.from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
251
+ const re = new RegExp(escaped, 'gi');
252
+ result = result.replace(re, r.to);
253
+ }
254
+ }
255
+ // Second pass: identifier-level replacements (LEADING, TRAILING, single-word EXACT)
256
+ const identifierReplacings = replacings.filter(r => !(r.type === 'EXACT' && (r.isPseudotext || r.from.includes(' ') || !/^[A-Z][A-Z0-9-]*$/i.test(r.from))));
257
+ if (identifierReplacings.length === 0)
258
+ return result;
259
+ return result.replace(RE_COBOL_IDENTIFIER, (match) => {
260
+ for (const r of identifierReplacings) {
261
+ const upper = match.toUpperCase();
262
+ const from = r.from.toUpperCase();
263
+ const to = r.to.toUpperCase();
264
+ switch (r.type) {
265
+ case 'LEADING':
266
+ if (upper.startsWith(from)) {
267
+ return to + match.substring(from.length);
268
+ }
269
+ break;
270
+ case 'TRAILING':
271
+ if (upper.endsWith(from)) {
272
+ return match.substring(0, match.length - from.length) + to;
273
+ }
274
+ break;
275
+ case 'EXACT':
276
+ if (upper === from) {
277
+ return to;
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ return match;
283
+ });
284
+ }
285
+ // ---------------------------------------------------------------------------
286
+ // Main expansion engine
287
+ // ---------------------------------------------------------------------------
288
+ /**
289
+ * Expand COBOL COPY statements by inlining copybook content.
290
+ *
291
+ * @param content - Source COBOL content (after preprocessCobolSource)
292
+ * @param filePath - Path of the source file (for diagnostics)
293
+ * @param resolveFile - Maps a COPY target name to a filesystem path, or null if not found
294
+ * @param readFile - Reads file content by path, or null if unreadable
295
+ * @param maxDepth - Maximum nesting depth for recursive expansion (default: 10)
296
+ * @returns Expanded content and resolution metadata
297
+ */
298
+ export function expandCopies(content, filePath, resolveFile, readFile, maxDepth = DEFAULT_MAX_DEPTH) {
299
+ const allResolutions = [];
300
+ const warnedCircular = new Set();
301
+ let totalExpansions = 0;
302
+ const MAX_TOTAL_EXPANSIONS = 500;
303
+ const expanded = expandRecursive(content, filePath, 0, new Set());
304
+ return {
305
+ expandedContent: expanded,
306
+ copyResolutions: allResolutions,
307
+ };
308
+ /**
309
+ * Recursively expand COPY statements in content.
310
+ *
311
+ * @param src - Source content to expand
312
+ * @param srcPath - Path of the file being expanded (for cycle detection logging)
313
+ * @param depth - Current recursion depth
314
+ * @param visited - Set of already-visited copybook paths (cycle detection)
315
+ */
316
+ function expandRecursive(src, srcPath, depth, visited) {
317
+ const rawLines = src.split(/\r?\n/);
318
+ const logicalLines = mergeLogicalLines(rawLines);
319
+ const copyStatements = parseCopyStatements(logicalLines);
320
+ // No COPY statements — return as-is
321
+ if (copyStatements.length === 0)
322
+ return src;
323
+ // Process COPY statements in reverse order so line numbers stay valid
324
+ // as we splice content
325
+ const outputLines = [...rawLines];
326
+ for (let ci = copyStatements.length - 1; ci >= 0; ci--) {
327
+ const cs = copyStatements[ci];
328
+ // Resolve the copybook path
329
+ const resolvedPath = resolveFile(cs.target);
330
+ // Record resolution metadata
331
+ allResolutions.push({
332
+ copyTarget: cs.target,
333
+ resolvedPath,
334
+ line: cs.startLine,
335
+ replacing: cs.replacing,
336
+ library: cs.library,
337
+ });
338
+ // Cannot resolve — keep original lines
339
+ if (resolvedPath === null) {
340
+ continue;
341
+ }
342
+ // Cycle detection
343
+ if (visited.has(resolvedPath)) {
344
+ if (!warnedCircular.has(resolvedPath)) {
345
+ warnedCircular.add(resolvedPath);
346
+ console.warn(`[cobol-copy-expander] Circular COPY detected: ${cs.target} (${resolvedPath}) ` +
347
+ `includes itself. Skipping expansion.`);
348
+ }
349
+ continue;
350
+ }
351
+ // Max depth exceeded — keep unexpanded
352
+ if (depth >= maxDepth) {
353
+ console.warn(`[cobol-copy-expander] Max expansion depth (${maxDepth}) reached for ` +
354
+ `COPY ${cs.target} in ${srcPath}. Skipping expansion.`);
355
+ continue;
356
+ }
357
+ // Guard against exponential breadth amplification (N copybooks each with N COPYs)
358
+ if (++totalExpansions > MAX_TOTAL_EXPANSIONS) {
359
+ if (!warnedCircular.has('__max_total__')) {
360
+ warnedCircular.add('__max_total__');
361
+ console.warn(`[cobol-copy-expander] Max total expansions (${MAX_TOTAL_EXPANSIONS}) reached ` +
362
+ `in ${srcPath}. Skipping further expansions.`);
363
+ }
364
+ continue;
365
+ }
366
+ // Read the copybook content
367
+ const copybookContent = readFile(resolvedPath);
368
+ if (copybookContent === null) {
369
+ continue;
370
+ }
371
+ // Apply REPLACING transformations
372
+ const replaced = applyReplacing(copybookContent, cs.replacing);
373
+ // Recurse into the copybook for nested COPYs
374
+ const nestedVisited = new Set(visited);
375
+ nestedVisited.add(resolvedPath);
376
+ const expandedCopybook = expandRecursive(replaced, resolvedPath, depth + 1, nestedVisited);
377
+ // Splice: replace the COPY statement lines with expanded content
378
+ // startLine/endLine are 1-based; convert to 0-based array index
379
+ const expansionLines = expandedCopybook.split('\n');
380
+ const removeCount = cs.endLine - cs.startLine + 1;
381
+ outputLines.splice(cs.startLine - 1, removeCount, ...expansionLines);
382
+ }
383
+ return outputLines.join('\n');
384
+ }
385
+ }