ctxman 3.0.0 → 3.1.0

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 (223) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +683 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +675 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +113 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -7,451 +7,1072 @@ import path from 'path';
7
7
  import GoMethodAnalyzer from './go-method-analyzer.js';
8
8
 
9
9
  class MethodAnalyzer {
10
- constructor() {
11
- this.goAnalyzer = new GoMethodAnalyzer();
10
+ constructor() {
11
+ this.goAnalyzer = new GoMethodAnalyzer();
12
+ }
13
+
14
+ extractMethods(content, filePath) {
15
+ const ext = path.extname(filePath).toLowerCase();
16
+
17
+ // Determine which analyzer to use based on file type
18
+ if (ext === '.rs') {
19
+ return this.extractRustMethods(content, filePath);
20
+ } else if (ext === '.go') {
21
+ return this.goAnalyzer.extractMethods(content, filePath);
22
+ } else if (ext === '.java') {
23
+ return this.extractJavaMethods(content, filePath);
24
+ } else if (ext === '.cs') {
25
+ return this.extractCSharpMethods(content, filePath);
26
+ } else if (ext === '.py') {
27
+ return this.extractPythonMethods(content, filePath);
28
+ } else if (ext === '.php') {
29
+ return this.extractPHPMethods(content, filePath);
30
+ } else if (ext === '.rb') {
31
+ return this.extractRubyMethods(content, filePath);
32
+ } else if (ext === '.kt' || ext === '.kts') {
33
+ return this.extractKotlinMethods(content, filePath);
34
+ } else if (ext === '.swift') {
35
+ return this.extractSwiftMethods(content, filePath);
36
+ } else if (ext === '.c' || ext === '.cpp' || ext === '.cc' || ext === '.h' || ext === '.hpp') {
37
+ return this.extractCPlusPlusMethods(content, filePath);
38
+ } else if (ext === '.scala') {
39
+ return this.extractScalaMethods(content, filePath);
40
+ } else {
41
+ return this.extractJavaScriptMethods(content, filePath);
12
42
  }
13
-
14
- extractMethods(content, filePath) {
15
- const ext = path.extname(filePath).toLowerCase();
16
-
17
- // Determine which analyzer to use based on file type
18
- if (ext === '.rs') {
19
- return this.extractRustMethods(content, filePath);
20
- } else if (ext === '.go') {
21
- return this.goAnalyzer.extractMethods(content, filePath);
22
- } else if (ext === '.java') {
23
- return this.extractJavaMethods(content, filePath);
24
- } else if (ext === '.cs') {
25
- return this.extractCSharpMethods(content, filePath);
26
- } else if (ext === '.py') {
27
- return this.extractPythonMethods(content, filePath);
28
- } else if (ext === '.php') {
29
- return this.extractPHPMethods(content, filePath);
30
- } else if (ext === '.rb') {
31
- return this.extractRubyMethods(content, filePath);
32
- } else if (ext === '.kt' || ext === '.kts') {
33
- return this.extractKotlinMethods(content, filePath);
34
- } else if (ext === '.swift') {
35
- return this.extractSwiftMethods(content, filePath);
36
- } else if (ext === '.c' || ext === '.cpp' || ext === '.cc' || ext === '.h' || ext === '.hpp') {
37
- return this.extractCPlusPlusMethods(content, filePath);
38
- } else if (ext === '.scala') {
39
- return this.extractScalaMethods(content, filePath);
43
+ }
44
+
45
+ extractJavaScriptMethods(content, filePath) {
46
+ // Support \w, $, _ for method names
47
+ const namePattern = '[\\w$_]+';
48
+ // Optional TypeScript annotation (`const f: Fn = ...`, `(...): Ret =>`), kept to one line.
49
+ // Parens are excluded so a ternary's `: call(...)` is not mistaken for a return type.
50
+ const typeAnnotation = '(?:\\s*:[^=;()\\n]+)?';
51
+
52
+ const patterns = [
53
+ // Order matters: More specific patterns first to avoid overlaps
54
+ {
55
+ regex: new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+(${namePattern})\\s*\\(`, 'g'),
56
+ type: 'function',
57
+ },
58
+ {
59
+ regex: new RegExp(`(${namePattern})\\s*:\\s*(?:async\\s+)?function\\s*\\(`, 'g'),
60
+ type: 'method',
61
+ },
62
+ {
63
+ regex: new RegExp(
64
+ `(?:const|let|var)\\s+(${namePattern})${typeAnnotation}\\s*=\\s*(?:async\\s+)?\\([^)]*\\)${typeAnnotation}\\s*=>`,
65
+ 'g'
66
+ ),
67
+ type: 'arrow',
68
+ },
69
+ { regex: new RegExp(`(get|set)\\s+(${namePattern})\\s*\\(`, 'g'), type: 'accessor' },
70
+ // Shorthand pattern (class methods) - must be last to avoid conflicts. The optional
71
+ // TypeScript return type starts right at `)`, so a ternary's `f(x) : {}` stays out, and
72
+ // holds no `$` or quotes, so template text like `error (${a}): ${b}` stays out too.
73
+ {
74
+ regex: new RegExp(
75
+ `(?:async\\s+)?(${namePattern})\\s*\\([^)]*\\)(?::[^=;()$'"\`\\n]+)?\\s*\\{`,
76
+ 'g'
77
+ ),
78
+ type: 'shorthand',
79
+ },
80
+ ];
81
+
82
+ return this.processPatterns(content, filePath, patterns);
83
+ }
84
+
85
+ extractJavaMethods(content, filePath) {
86
+ const namePattern = '[\\w]+';
87
+
88
+ const patterns = [
89
+ // Public/private/protected methods with return type
90
+ {
91
+ regex: new RegExp(
92
+ `(?:public|private|protected|static|final|synchronized|native|abstract|\\s)+(?:<[^>]+>\\s+)?(?:[\\w<>\\[\\],\\s]+)\\s+(${namePattern})\\s*\\(`,
93
+ 'g'
94
+ ),
95
+ type: 'method',
96
+ },
97
+ // Constructor (same name as class)
98
+ {
99
+ regex: new RegExp(
100
+ `(?:public|private|protected)?\\s+(${namePattern})\\s*\\([^)]*\\)\\s*(?:throws\\s+[\\w,\\s]+)?\\s*\\{`,
101
+ 'g'
102
+ ),
103
+ type: 'constructor',
104
+ },
105
+ ];
106
+
107
+ return this.processPatterns(content, filePath, patterns, true);
108
+ }
109
+
110
+ extractCSharpMethods(content, filePath) {
111
+ const namePattern = '[\\w]+';
112
+
113
+ const patterns = [
114
+ // C# method patterns with modifiers and return types
115
+ // Matches: public/private/protected/internal static/async returnType MethodName(params) {
116
+ {
117
+ regex: new RegExp(
118
+ `(?:public|private|protected|internal)?\\s*` +
119
+ `(?:static|virtual|override|abstract|async)?\\s*` +
120
+ `(?:partial)?\\s*` +
121
+ `(?:void|bool|int|string|double|float|long|decimal|object|var|Task|${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
122
+ `(${namePattern})\\s*` +
123
+ `(?:<[^>]+>)?\\s*` + // Generic type parameters
124
+ `\\([^)]*\\)\\s*` +
125
+ `(?:where\\s+[^{]+)?\\s*` + // Generic constraints
126
+ `\\{`,
127
+ 'g'
128
+ ),
129
+ type: 'method',
130
+ },
131
+ // Property getters/setters
132
+ {
133
+ regex: new RegExp(
134
+ `(?:public|private|protected|internal)?\\s*` +
135
+ `(?:static|virtual|override|abstract)?\\s*` +
136
+ `(?:${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
137
+ `(${namePattern})\\s*` +
138
+ `\\{\\s*(?:get|set)`,
139
+ 'g'
140
+ ),
141
+ type: 'property',
142
+ },
143
+ // Expression-bodied members (C# 6.0+)
144
+ // Matches: public Type MethodName() => expression;
145
+ {
146
+ regex: new RegExp(
147
+ `(?:public|private|protected|internal)?\\s*` +
148
+ `(?:static|virtual|override|abstract)?\\s*` +
149
+ `(?:${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
150
+ `(${namePattern})\\s*` +
151
+ `(?:<[^>]+>)?\\s*` +
152
+ `\\([^)]*\\)\\s*=>`,
153
+ 'g'
154
+ ),
155
+ type: 'expression-bodied',
156
+ },
157
+ ];
158
+
159
+ return this.processPatterns(content, filePath, patterns, false, true);
160
+ }
161
+
162
+ processPatterns(
163
+ content,
164
+ filePath,
165
+ patterns,
166
+ isJava = false,
167
+ isCSharp = false,
168
+ isPython = false,
169
+ isRuby = false,
170
+ isKotlin = false,
171
+ isSwift = false,
172
+ isScala = false
173
+ ) {
174
+ const methodsMap = new Map();
175
+ const processedLines = new Map(); // Track processed lines to avoid duplicates
176
+
177
+ patterns.forEach(({ regex, type }) => {
178
+ let match;
179
+ while ((match = regex.exec(content)) !== null) {
180
+ let methodName;
181
+ let accessorType = '';
182
+
183
+ if (type === 'accessor') {
184
+ accessorType = match[1];
185
+ methodName = match[2];
40
186
  } else {
41
- return this.extractJavaScriptMethods(content, filePath);
187
+ methodName = match[1];
42
188
  }
43
- }
44
-
45
- extractJavaScriptMethods(content, filePath) {
46
- // Support \w, $, _ for method names
47
- const namePattern = '[\\w$_]+';
48
-
49
- const patterns = [
50
- // Order matters: More specific patterns first to avoid overlaps
51
- { regex: new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+(${namePattern})\\s*\\(`, 'g'), type: 'function' },
52
- { regex: new RegExp(`(${namePattern})\\s*:\\s*(?:async\\s+)?function\\s*\\(`, 'g'), type: 'method' },
53
- { regex: new RegExp(`(?:const|let|var)\\s+(${namePattern})\\s*=\\s*(?:async\\s+)?\\([^)]*\\)\\s*=>`, 'g'), type: 'arrow' },
54
- { regex: new RegExp(`(get|set)\\s+(${namePattern})\\s*\\(`, 'g'), type: 'accessor' },
55
- // Shorthand pattern (class methods) - must be last to avoid conflicts
56
- { regex: new RegExp(`(?:async\\s+)?(${namePattern})\\s*\\([^)]*\\)\\s*\\{`, 'g'), type: 'shorthand' },
57
- ];
58
-
59
- return this.processPatterns(content, filePath, patterns);
60
- }
61
-
62
- extractJavaMethods(content, filePath) {
63
- const namePattern = '[\\w]+';
64
-
65
- const patterns = [
66
- // Public/private/protected methods with return type
67
- {
68
- regex: new RegExp(`(?:public|private|protected|static|final|synchronized|native|abstract|\\s)+(?:<[^>]+>\\s+)?(?:[\\w<>\\[\\],\\s]+)\\s+(${namePattern})\\s*\\(`, 'g'),
69
- type: 'method'
70
- },
71
- // Constructor (same name as class)
72
- {
73
- regex: new RegExp(`(?:public|private|protected)?\\s+(${namePattern})\\s*\\([^)]*\\)\\s*(?:throws\\s+[\\w,\\s]+)?\\s*\\{`, 'g'),
74
- type: 'constructor'
75
- },
76
- ];
77
-
78
- return this.processPatterns(content, filePath, patterns, true);
79
- }
80
-
81
- extractCSharpMethods(content, filePath) {
82
- const namePattern = '[\\w]+';
83
-
84
- const patterns = [
85
- // C# method patterns with modifiers and return types
86
- // Matches: public/private/protected/internal static/async returnType MethodName(params) {
87
- {
88
- regex: new RegExp(
89
- `(?:public|private|protected|internal)?\\s*` +
90
- `(?:static|virtual|override|abstract|async)?\\s*` +
91
- `(?:partial)?\\s*` +
92
- `(?:void|bool|int|string|double|float|long|decimal|object|var|Task|${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
93
- `(${namePattern})\\s*` +
94
- `(?:<[^>]+>)?\\s*` + // Generic type parameters
95
- `\\([^)]*\\)\\s*` +
96
- `(?:where\\s+[^{]+)?\\s*` + // Generic constraints
97
- `\\{`,
98
- 'g'
99
- ),
100
- type: 'method'
101
- },
102
- // Property getters/setters
103
- {
104
- regex: new RegExp(
105
- `(?:public|private|protected|internal)?\\s*` +
106
- `(?:static|virtual|override|abstract)?\\s*` +
107
- `(?:${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
108
- `(${namePattern})\\s*` +
109
- `\\{\\s*(?:get|set)`,
110
- 'g'
111
- ),
112
- type: 'property'
113
- },
114
- // Expression-bodied members (C# 6.0+)
115
- // Matches: public Type MethodName() => expression;
116
- {
117
- regex: new RegExp(
118
- `(?:public|private|protected|internal)?\\s*` +
119
- `(?:static|virtual|override|abstract)?\\s*` +
120
- `(?:${namePattern}(?:<[^>]+>)?(?:\\[\\])?)?\\s+` +
121
- `(${namePattern})\\s*` +
122
- `(?:<[^>]+>)?\\s*` +
123
- `\\([^)]*\\)\\s*=>`,
124
- 'g'
125
- ),
126
- type: 'expression-bodied'
127
- }
128
- ];
129
-
130
- return this.processPatterns(content, filePath, patterns, false, true);
131
- }
132
-
133
- processPatterns(content, filePath, patterns, isJava = false, isCSharp = false, isPython = false, isRuby = false, isKotlin = false, isSwift = false, isScala = false) {
134
- const methodsMap = new Map();
135
- const processedLines = new Map(); // Track processed lines to avoid duplicates
136
-
137
- patterns.forEach(({ regex, type }) => {
138
- let match;
139
- while ((match = regex.exec(content)) !== null) {
140
- let methodName;
141
- let accessorType = '';
142
-
143
- if (type === 'accessor') {
144
- accessorType = match[1];
145
- methodName = match[2];
146
- } else {
147
- methodName = match[1];
148
- }
149
-
150
- const line = this.getLineNumber(content, match.index + match[0].indexOf(methodName));
151
-
152
- // Use appropriate keyword check based on language
153
- let keywordCheck = false;
154
- if (isCSharp) {
155
- keywordCheck = !this.isCSharpKeyword(methodName);
156
- } else if (isPython) {
157
- keywordCheck = !this.isPythonKeyword(methodName);
158
- } else if (isRuby) {
159
- keywordCheck = !this.isRubyKeyword(methodName);
160
- } else if (isKotlin) {
161
- keywordCheck = !this.isKotlinKeyword(methodName);
162
- } else if (isSwift) {
163
- keywordCheck = !this.isSwiftKeyword(methodName);
164
- } else if (isScala) {
165
- keywordCheck = !this.isScalaKeyword(methodName);
166
- } else {
167
- keywordCheck = !this.isKeyword(methodName, isJava);
168
- }
169
-
170
- if (methodName && keywordCheck) {
171
- const key = type === 'accessor'
172
- ? `${accessorType}_${methodName}:${line}`
173
- : `${methodName}:${line}`;
174
-
175
- // Skip if this line was already processed for this method name
176
- const lineKey = `${methodName}:${line}`;
177
- if (processedLines.has(lineKey)) {
178
- continue;
179
- }
180
-
181
- if (!methodsMap.has(key)) {
182
- methodsMap.set(key, {
183
- name: type === 'accessor' ? `${accessorType} ${methodName}` : methodName,
184
- line: line,
185
- file: path.relative(process.cwd(), filePath)
186
- });
187
- processedLines.set(lineKey, true);
188
- }
189
- }
190
- }
191
- });
192
-
193
- return Array.from(methodsMap.values());
194
- }
195
-
196
- extractPythonMethods(content, filePath) {
197
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
198
-
199
- const patterns = [
200
- // def function_name or async def function_name
201
- { regex: new RegExp(`^\\s*(?:async\\s+)?def\\s+(${namePattern})\\s*\\(`, 'gm'), type: 'function' },
202
- // @staticmethod, @classmethod decorators
203
- { regex: new RegExp(`@(?:staticmethod|classmethod)\\s+def\\s+(${namePattern})\\s*\\(`, 'gm'), type: 'method' },
204
- ];
205
-
206
- return this.processPatterns(content, filePath, patterns, false, false, true);
207
- }
208
-
209
- extractPHPMethods(content, filePath) {
210
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
211
-
212
- const patterns = [
213
- // function function_name
214
- { regex: new RegExp(`function\\s+(${namePattern})\\s*\\(`, 'g'), type: 'function' },
215
- // public/private/protected function method_name
216
- { regex: new RegExp(`(?:public|private|protected)?\\s*(?:static)?\\s*function\\s+(${namePattern})\\s*\\(`, 'g'), type: 'method' },
217
- ];
218
-
219
- return this.processPatterns(content, filePath, patterns);
220
- }
221
-
222
- extractRubyMethods(content, filePath) {
223
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*[?!]?';
224
-
225
- const patterns = [
226
- // def method_name or def self.method_name
227
- { regex: new RegExp(`^\\s*def\\s+(?:self\\.)?(${namePattern})\\s*(?:\\(|$)`, 'gm'), type: 'method' },
228
- ];
229
-
230
- return this.processPatterns(content, filePath, patterns, false, false, false, true);
231
- }
232
-
233
- extractKotlinMethods(content, filePath) {
234
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
235
-
236
- const patterns = [
237
- // fun functionName or fun ClassName.functionName
238
- { regex: new RegExp(`(?:public|private|protected|internal)?\\s*(?:suspend\\s+)?(?:inline\\s+)?fun\\s+(?:<[^>]+>\\s+)?(?:[\\w.]+\\.)?\\s*(${namePattern})\\s*(?:<[^>]+>)?\\s*\\(`, 'g'), type: 'function' },
239
- ];
240
-
241
- return this.processPatterns(content, filePath, patterns, false, false, false, false, true);
242
- }
243
-
244
- extractSwiftMethods(content, filePath) {
245
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
246
-
247
- const patterns = [
248
- // func functionName
249
- { regex: new RegExp(`(?:public|private|internal|fileprivate)?\\s*(?:static|class)?\\s*func\\s+(${namePattern})\\s*(?:<[^>]+>)?\\s*\\(`, 'g'), type: 'function' },
250
- // init (constructors)
251
- { regex: new RegExp(`(?:public|private|internal|fileprivate)?\\s*(?:convenience\\s+)?init\\s*\\(`, 'g'), type: 'init' },
252
- ];
253
-
254
- return this.processPatterns(content, filePath, patterns, false, false, false, false, false, true);
255
- }
256
189
 
257
- extractCPlusPlusMethods(content, filePath) {
258
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
259
-
260
- const patterns = [
261
- // Return type function_name( or ClassName::function_name(
262
- { regex: new RegExp(`(?:virtual\\s+)?(?:static\\s+)?(?:inline\\s+)?(?:[\\w:]+\\s+)?(?:[\\w:]+::)?(${namePattern})\\s*\\([^)]*\\)\\s*(?:const)?\\s*(?:override)?\\s*(?:final)?\\s*\\{`, 'g'), type: 'function' },
263
- // Constructor: ClassName::ClassName(
264
- { regex: new RegExp(`(${namePattern})\\s*\\([^)]*\\)\\s*:\\s*`, 'g'), type: 'constructor' },
265
- ];
266
-
267
- return this.processPatterns(content, filePath, patterns);
268
- }
269
-
270
- extractScalaMethods(content, filePath) {
271
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
272
-
273
- const patterns = [
274
- // def methodName
275
- { regex: new RegExp(`(?:override\\s+)?def\\s+(${namePattern})\\s*(?:\\[|\\(|:)`, 'g'), type: 'method' },
276
- // val/var with function assignment
277
- { regex: new RegExp(`(?:val|var)\\s+(${namePattern})\\s*=\\s*\\([^)]*\\)\\s*=>`, 'g'), type: 'function' },
278
- ];
279
-
280
- return this.processPatterns(content, filePath, patterns, false, false, false, false, false, false, true);
281
- }
282
-
283
- extractRustMethods(content, filePath) {
284
- // Rust method name pattern (snake_case and alphanumeric)
285
- const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
286
-
287
- const patterns = [
288
- // pub fn function_name or fn function_name
289
- { regex: new RegExp(`(?:pub\\s+)?(?:async\\s+)?(?:const\\s+)?(?:unsafe\\s+)?fn\\s+(${namePattern})\\s*[<(]`, 'g'), type: 'function' },
290
- // impl methods: fn method_name (inside impl blocks)
291
- { regex: new RegExp(`^\\s*(?:pub\\s+)?(?:async\\s+)?(?:const\\s+)?(?:unsafe\\s+)?fn\\s+(${namePattern})\\s*[<(]`, 'gm'), type: 'method' },
292
- ];
293
-
294
- const methodsMap = new Map();
295
- const processedLines = new Map();
296
-
297
- patterns.forEach(({ regex, type }) => {
298
- let match;
299
- while ((match = regex.exec(content)) !== null) {
300
- const methodName = match[1];
301
- const line = this.getLineNumber(content, match.index + match[0].indexOf(methodName));
302
-
303
- if (methodName && !this.isRustKeyword(methodName)) {
304
- const key = `${methodName}:${line}`;
305
- const lineKey = `${methodName}:${line}`;
306
-
307
- if (processedLines.has(lineKey)) {
308
- continue;
309
- }
310
-
311
- if (!methodsMap.has(key)) {
312
- methodsMap.set(key, {
313
- name: methodName,
314
- line: line,
315
- file: path.relative(process.cwd(), filePath)
316
- });
317
- processedLines.set(lineKey, true);
318
- }
319
- }
320
- }
321
- });
322
-
323
- return Array.from(methodsMap.values());
324
- }
325
-
326
- getLineNumber(content, index) {
327
- return content.substring(0, index).split('\n').length;
328
- }
329
-
330
- isKeyword(name, isJava = false) {
331
- const jsKeywords = new Set([
332
- 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default',
333
- 'try', 'catch', 'finally', 'throw', 'return', 'break', 'continue',
334
- 'class', 'extends', 'super', 'this', 'new', 'typeof', 'instanceof',
335
- 'var', 'let', 'const', 'function', 'async', 'await', 'export', 'import'
336
- ]);
337
-
338
- const javaKeywords = new Set([
339
- 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default',
340
- 'try', 'catch', 'finally', 'throw', 'return', 'break', 'continue',
341
- 'class', 'extends', 'super', 'this', 'new', 'instanceof',
342
- 'abstract', 'assert', 'boolean', 'byte', 'char', 'double', 'enum',
343
- 'final', 'float', 'implements', 'import', 'int', 'interface', 'long',
344
- 'native', 'package', 'private', 'protected', 'public', 'short', 'static',
345
- 'strictfp', 'synchronized', 'throws', 'transient', 'void', 'volatile',
346
- // Common type names to skip
347
- 'String', 'Integer', 'Boolean', 'Double', 'Float', 'Long', 'Short',
348
- 'Byte', 'Character', 'List', 'Map', 'Set', 'ArrayList', 'HashMap',
349
- 'HashSet', 'Object', 'Exception'
350
- ]);
351
-
352
- return isJava ? javaKeywords.has(name) : jsKeywords.has(name);
353
- }
354
-
355
- isCSharpKeyword(name) {
356
- const keywords = new Set([
357
- 'if', 'else', 'for', 'foreach', 'while', 'do', 'switch', 'case', 'default',
358
- 'try', 'catch', 'finally', 'throw', 'return', 'break', 'continue',
359
- 'class', 'interface', 'struct', 'enum', 'this', 'base', 'new', 'typeof',
360
- 'var', 'using', 'namespace', 'async', 'await', 'lock', 'yield',
361
- 'public', 'private', 'protected', 'internal', 'static', 'virtual',
362
- 'override', 'abstract', 'sealed', 'partial', 'readonly', 'const'
363
- ]);
364
- return keywords.has(name);
365
- }
366
-
367
- isRustKeyword(name) {
368
- const keywords = new Set([
369
- 'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern',
370
- 'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match',
371
- 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static',
372
- 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where',
373
- 'while', 'async', 'await', 'dyn', 'abstract', 'become', 'box', 'do',
374
- 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual', 'yield'
375
- ]);
376
- return keywords.has(name);
377
- }
378
-
379
- isPythonKeyword(name) {
380
- const keywords = new Set([
381
- 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
382
- 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
383
- 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
384
- 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
385
- 'try', 'while', 'with', 'yield', '__init__', '__main__'
386
- ]);
387
- return keywords.has(name);
388
- }
190
+ const line = this.getLineNumber(content, match.index + match[0].indexOf(methodName));
191
+
192
+ // Use appropriate keyword check based on language
193
+ let keywordCheck = false;
194
+ if (isCSharp) {
195
+ keywordCheck = !this.isCSharpKeyword(methodName);
196
+ } else if (isPython) {
197
+ keywordCheck = !this.isPythonKeyword(methodName);
198
+ } else if (isRuby) {
199
+ keywordCheck = !this.isRubyKeyword(methodName);
200
+ } else if (isKotlin) {
201
+ keywordCheck = !this.isKotlinKeyword(methodName);
202
+ } else if (isSwift) {
203
+ keywordCheck = !this.isSwiftKeyword(methodName);
204
+ } else if (isScala) {
205
+ keywordCheck = !this.isScalaKeyword(methodName);
206
+ } else {
207
+ keywordCheck = !this.isKeyword(methodName, isJava);
208
+ }
389
209
 
390
- isRubyKeyword(name) {
391
- const keywords = new Set([
392
- 'alias', 'and', 'begin', 'break', 'case', 'class', 'def', 'defined?',
393
- 'do', 'else', 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in',
394
- 'module', 'next', 'nil', 'not', 'or', 'redo', 'rescue', 'retry',
395
- 'return', 'self', 'super', 'then', 'true', 'undef', 'unless', 'until',
396
- 'when', 'while', 'yield', 'initialize'
397
- ]);
398
- return keywords.has(name);
399
- }
210
+ if (methodName && keywordCheck) {
211
+ const key =
212
+ type === 'accessor' ? `${accessorType}_${methodName}:${line}` : `${methodName}:${line}`;
213
+
214
+ // Skip if this line was already processed for this method name
215
+ const lineKey = `${methodName}:${line}`;
216
+ if (processedLines.has(lineKey)) {
217
+ continue;
218
+ }
219
+
220
+ if (!methodsMap.has(key)) {
221
+ methodsMap.set(key, {
222
+ name: type === 'accessor' ? `${accessorType} ${methodName}` : methodName,
223
+ line: line,
224
+ file: path.relative(process.cwd(), filePath),
225
+ });
226
+ processedLines.set(lineKey, true);
227
+ }
228
+ }
229
+ }
230
+ });
231
+
232
+ return Array.from(methodsMap.values());
233
+ }
234
+
235
+ extractPythonMethods(content, filePath) {
236
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
237
+
238
+ const patterns = [
239
+ // def function_name or async def function_name
240
+ {
241
+ regex: new RegExp(`^\\s*(?:async\\s+)?def\\s+(${namePattern})\\s*\\(`, 'gm'),
242
+ type: 'function',
243
+ },
244
+ // @staticmethod, @classmethod decorators
245
+ {
246
+ regex: new RegExp(`@(?:staticmethod|classmethod)\\s+def\\s+(${namePattern})\\s*\\(`, 'gm'),
247
+ type: 'method',
248
+ },
249
+ ];
250
+
251
+ return this.processPatterns(content, filePath, patterns, false, false, true);
252
+ }
253
+
254
+ extractPHPMethods(content, filePath) {
255
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
256
+
257
+ const patterns = [
258
+ // function function_name
259
+ { regex: new RegExp(`function\\s+(${namePattern})\\s*\\(`, 'g'), type: 'function' },
260
+ // public/private/protected function method_name
261
+ {
262
+ regex: new RegExp(
263
+ `(?:public|private|protected)?\\s*(?:static)?\\s*function\\s+(${namePattern})\\s*\\(`,
264
+ 'g'
265
+ ),
266
+ type: 'method',
267
+ },
268
+ ];
269
+
270
+ return this.processPatterns(content, filePath, patterns);
271
+ }
272
+
273
+ extractRubyMethods(content, filePath) {
274
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*[?!]?';
275
+
276
+ const patterns = [
277
+ // def method_name or def self.method_name
278
+ {
279
+ regex: new RegExp(`^\\s*def\\s+(?:self\\.)?(${namePattern})\\s*(?:\\(|$)`, 'gm'),
280
+ type: 'method',
281
+ },
282
+ ];
283
+
284
+ return this.processPatterns(content, filePath, patterns, false, false, false, true);
285
+ }
286
+
287
+ extractKotlinMethods(content, filePath) {
288
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
289
+
290
+ const patterns = [
291
+ // fun functionName or fun ClassName.functionName
292
+ {
293
+ regex: new RegExp(
294
+ `(?:public|private|protected|internal)?\\s*(?:suspend\\s+)?(?:inline\\s+)?fun\\s+(?:<[^>]+>\\s+)?(?:[\\w.]+\\.)?\\s*(${namePattern})\\s*(?:<[^>]+>)?\\s*\\(`,
295
+ 'g'
296
+ ),
297
+ type: 'function',
298
+ },
299
+ ];
300
+
301
+ return this.processPatterns(content, filePath, patterns, false, false, false, false, true);
302
+ }
303
+
304
+ extractSwiftMethods(content, filePath) {
305
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
306
+
307
+ const patterns = [
308
+ // func functionName
309
+ {
310
+ regex: new RegExp(
311
+ `(?:public|private|internal|fileprivate)?\\s*(?:static|class)?\\s*func\\s+(${namePattern})\\s*(?:<[^>]+>)?\\s*\\(`,
312
+ 'g'
313
+ ),
314
+ type: 'function',
315
+ },
316
+ // init (constructors), including failable `init?(`; the lookbehind skips
317
+ // `super.init(` / `self.init(` calls and names that merely end in "init"
318
+ {
319
+ regex: new RegExp(
320
+ `(?:public|private|internal|fileprivate)?\\s*(?:convenience\\s+)?(?<![\\w.])(init)[?!]?\\s*(?:<[^>]+>)?\\s*\\(`,
321
+ 'g'
322
+ ),
323
+ type: 'init',
324
+ },
325
+ ];
326
+
327
+ return this.processPatterns(
328
+ content,
329
+ filePath,
330
+ patterns,
331
+ false,
332
+ false,
333
+ false,
334
+ false,
335
+ false,
336
+ true
337
+ );
338
+ }
339
+
340
+ extractCPlusPlusMethods(content, filePath) {
341
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
342
+
343
+ const patterns = [
344
+ // Return type function_name( or ClassName::function_name(
345
+ {
346
+ regex: new RegExp(
347
+ `(?:virtual\\s+)?(?:static\\s+)?(?:inline\\s+)?(?:[\\w:]+\\s+)?(?:[\\w:]+::)?(${namePattern})\\s*\\([^)]*\\)\\s*(?:const)?\\s*(?:override)?\\s*(?:final)?\\s*\\{`,
348
+ 'g'
349
+ ),
350
+ type: 'function',
351
+ },
352
+ // Constructor: ClassName::ClassName(
353
+ { regex: new RegExp(`(${namePattern})\\s*\\([^)]*\\)\\s*:\\s*`, 'g'), type: 'constructor' },
354
+ ];
355
+
356
+ return this.processPatterns(content, filePath, patterns);
357
+ }
358
+
359
+ extractScalaMethods(content, filePath) {
360
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
361
+
362
+ const patterns = [
363
+ // def methodName
364
+ {
365
+ regex: new RegExp(`(?:override\\s+)?def\\s+(${namePattern})\\s*(?:\\[|\\(|:)`, 'g'),
366
+ type: 'method',
367
+ },
368
+ // val/var with function assignment
369
+ {
370
+ regex: new RegExp(`(?:val|var)\\s+(${namePattern})\\s*=\\s*\\([^)]*\\)\\s*=>`, 'g'),
371
+ type: 'function',
372
+ },
373
+ ];
374
+
375
+ return this.processPatterns(
376
+ content,
377
+ filePath,
378
+ patterns,
379
+ false,
380
+ false,
381
+ false,
382
+ false,
383
+ false,
384
+ false,
385
+ true
386
+ );
387
+ }
388
+
389
+ extractRustMethods(content, filePath) {
390
+ // Rust method name pattern (snake_case and alphanumeric)
391
+ const namePattern = '[a-zA-Z_][a-zA-Z0-9_]*';
392
+
393
+ const patterns = [
394
+ // pub fn function_name or fn function_name
395
+ {
396
+ regex: new RegExp(
397
+ `(?:pub\\s+)?(?:async\\s+)?(?:const\\s+)?(?:unsafe\\s+)?fn\\s+(${namePattern})\\s*[<(]`,
398
+ 'g'
399
+ ),
400
+ type: 'function',
401
+ },
402
+ // impl methods: fn method_name (inside impl blocks)
403
+ {
404
+ regex: new RegExp(
405
+ `^\\s*(?:pub\\s+)?(?:async\\s+)?(?:const\\s+)?(?:unsafe\\s+)?fn\\s+(${namePattern})\\s*[<(]`,
406
+ 'gm'
407
+ ),
408
+ type: 'method',
409
+ },
410
+ ];
411
+
412
+ const methodsMap = new Map();
413
+ const processedLines = new Map();
414
+
415
+ patterns.forEach(({ regex, type: _type }) => {
416
+ let match;
417
+ while ((match = regex.exec(content)) !== null) {
418
+ const methodName = match[1];
419
+ const line = this.getLineNumber(content, match.index + match[0].indexOf(methodName));
420
+
421
+ if (methodName && !this.isRustKeyword(methodName)) {
422
+ const key = `${methodName}:${line}`;
423
+ const lineKey = `${methodName}:${line}`;
424
+
425
+ if (processedLines.has(lineKey)) {
426
+ continue;
427
+ }
428
+
429
+ if (!methodsMap.has(key)) {
430
+ methodsMap.set(key, {
431
+ name: methodName,
432
+ line: line,
433
+ file: path.relative(process.cwd(), filePath),
434
+ });
435
+ processedLines.set(lineKey, true);
436
+ }
437
+ }
438
+ }
439
+ });
440
+
441
+ return Array.from(methodsMap.values());
442
+ }
443
+
444
+ getLineNumber(content, index) {
445
+ return content.substring(0, index).split('\n').length;
446
+ }
447
+
448
+ isKeyword(name, isJava = false) {
449
+ const jsKeywords = new Set([
450
+ 'if',
451
+ 'else',
452
+ 'for',
453
+ 'while',
454
+ 'do',
455
+ 'switch',
456
+ 'case',
457
+ 'default',
458
+ 'try',
459
+ 'catch',
460
+ 'finally',
461
+ 'throw',
462
+ 'return',
463
+ 'break',
464
+ 'continue',
465
+ 'class',
466
+ 'extends',
467
+ 'super',
468
+ 'this',
469
+ 'new',
470
+ 'typeof',
471
+ 'instanceof',
472
+ 'var',
473
+ 'let',
474
+ 'const',
475
+ 'function',
476
+ 'async',
477
+ 'await',
478
+ 'export',
479
+ 'import',
480
+ ]);
481
+
482
+ const javaKeywords = new Set([
483
+ 'if',
484
+ 'else',
485
+ 'for',
486
+ 'while',
487
+ 'do',
488
+ 'switch',
489
+ 'case',
490
+ 'default',
491
+ 'try',
492
+ 'catch',
493
+ 'finally',
494
+ 'throw',
495
+ 'return',
496
+ 'break',
497
+ 'continue',
498
+ 'class',
499
+ 'extends',
500
+ 'super',
501
+ 'this',
502
+ 'new',
503
+ 'instanceof',
504
+ 'abstract',
505
+ 'assert',
506
+ 'boolean',
507
+ 'byte',
508
+ 'char',
509
+ 'double',
510
+ 'enum',
511
+ 'final',
512
+ 'float',
513
+ 'implements',
514
+ 'import',
515
+ 'int',
516
+ 'interface',
517
+ 'long',
518
+ 'native',
519
+ 'package',
520
+ 'private',
521
+ 'protected',
522
+ 'public',
523
+ 'short',
524
+ 'static',
525
+ 'strictfp',
526
+ 'synchronized',
527
+ 'throws',
528
+ 'transient',
529
+ 'void',
530
+ 'volatile',
531
+ // Common type names to skip
532
+ 'String',
533
+ 'Integer',
534
+ 'Boolean',
535
+ 'Double',
536
+ 'Float',
537
+ 'Long',
538
+ 'Short',
539
+ 'Byte',
540
+ 'Character',
541
+ 'List',
542
+ 'Map',
543
+ 'Set',
544
+ 'ArrayList',
545
+ 'HashMap',
546
+ 'HashSet',
547
+ 'Object',
548
+ 'Exception',
549
+ ]);
550
+
551
+ return isJava ? javaKeywords.has(name) : jsKeywords.has(name);
552
+ }
553
+
554
+ isCSharpKeyword(name) {
555
+ const keywords = new Set([
556
+ 'if',
557
+ 'else',
558
+ 'for',
559
+ 'foreach',
560
+ 'while',
561
+ 'do',
562
+ 'switch',
563
+ 'case',
564
+ 'default',
565
+ 'try',
566
+ 'catch',
567
+ 'finally',
568
+ 'throw',
569
+ 'return',
570
+ 'break',
571
+ 'continue',
572
+ 'class',
573
+ 'interface',
574
+ 'struct',
575
+ 'enum',
576
+ 'this',
577
+ 'base',
578
+ 'new',
579
+ 'typeof',
580
+ 'var',
581
+ 'using',
582
+ 'namespace',
583
+ 'async',
584
+ 'await',
585
+ 'lock',
586
+ 'yield',
587
+ 'public',
588
+ 'private',
589
+ 'protected',
590
+ 'internal',
591
+ 'static',
592
+ 'virtual',
593
+ 'override',
594
+ 'abstract',
595
+ 'sealed',
596
+ 'partial',
597
+ 'readonly',
598
+ 'const',
599
+ ]);
600
+ return keywords.has(name);
601
+ }
602
+
603
+ isRustKeyword(name) {
604
+ const keywords = new Set([
605
+ 'as',
606
+ 'break',
607
+ 'const',
608
+ 'continue',
609
+ 'crate',
610
+ 'else',
611
+ 'enum',
612
+ 'extern',
613
+ 'false',
614
+ 'fn',
615
+ 'for',
616
+ 'if',
617
+ 'impl',
618
+ 'in',
619
+ 'let',
620
+ 'loop',
621
+ 'match',
622
+ 'mod',
623
+ 'move',
624
+ 'mut',
625
+ 'pub',
626
+ 'ref',
627
+ 'return',
628
+ 'self',
629
+ 'Self',
630
+ 'static',
631
+ 'struct',
632
+ 'super',
633
+ 'trait',
634
+ 'true',
635
+ 'type',
636
+ 'unsafe',
637
+ 'use',
638
+ 'where',
639
+ 'while',
640
+ 'async',
641
+ 'await',
642
+ 'dyn',
643
+ 'abstract',
644
+ 'become',
645
+ 'box',
646
+ 'do',
647
+ 'final',
648
+ 'macro',
649
+ 'override',
650
+ 'priv',
651
+ 'typeof',
652
+ 'unsized',
653
+ 'virtual',
654
+ 'yield',
655
+ ]);
656
+ return keywords.has(name);
657
+ }
658
+
659
+ isPythonKeyword(name) {
660
+ const keywords = new Set([
661
+ 'False',
662
+ 'None',
663
+ 'True',
664
+ 'and',
665
+ 'as',
666
+ 'assert',
667
+ 'async',
668
+ 'await',
669
+ 'break',
670
+ 'class',
671
+ 'continue',
672
+ 'def',
673
+ 'del',
674
+ 'elif',
675
+ 'else',
676
+ 'except',
677
+ 'finally',
678
+ 'for',
679
+ 'from',
680
+ 'global',
681
+ 'if',
682
+ 'import',
683
+ 'in',
684
+ 'is',
685
+ 'lambda',
686
+ 'nonlocal',
687
+ 'not',
688
+ 'or',
689
+ 'pass',
690
+ 'raise',
691
+ 'return',
692
+ 'try',
693
+ 'while',
694
+ 'with',
695
+ 'yield',
696
+ '__init__',
697
+ '__main__',
698
+ ]);
699
+ return keywords.has(name);
700
+ }
701
+
702
+ isRubyKeyword(name) {
703
+ const keywords = new Set([
704
+ 'alias',
705
+ 'and',
706
+ 'begin',
707
+ 'break',
708
+ 'case',
709
+ 'class',
710
+ 'def',
711
+ 'defined?',
712
+ 'do',
713
+ 'else',
714
+ 'elsif',
715
+ 'end',
716
+ 'ensure',
717
+ 'false',
718
+ 'for',
719
+ 'if',
720
+ 'in',
721
+ 'module',
722
+ 'next',
723
+ 'nil',
724
+ 'not',
725
+ 'or',
726
+ 'redo',
727
+ 'rescue',
728
+ 'retry',
729
+ 'return',
730
+ 'self',
731
+ 'super',
732
+ 'then',
733
+ 'true',
734
+ 'undef',
735
+ 'unless',
736
+ 'until',
737
+ 'when',
738
+ 'while',
739
+ 'yield',
740
+ 'initialize',
741
+ ]);
742
+ return keywords.has(name);
743
+ }
744
+
745
+ isKotlinKeyword(name) {
746
+ const keywords = new Set([
747
+ 'as',
748
+ 'break',
749
+ 'class',
750
+ 'continue',
751
+ 'do',
752
+ 'else',
753
+ 'false',
754
+ 'for',
755
+ 'fun',
756
+ 'if',
757
+ 'in',
758
+ 'interface',
759
+ 'is',
760
+ 'null',
761
+ 'object',
762
+ 'package',
763
+ 'return',
764
+ 'super',
765
+ 'this',
766
+ 'throw',
767
+ 'true',
768
+ 'try',
769
+ 'typealias',
770
+ 'typeof',
771
+ 'val',
772
+ 'var',
773
+ 'when',
774
+ 'while',
775
+ 'by',
776
+ 'catch',
777
+ 'constructor',
778
+ 'delegate',
779
+ 'dynamic',
780
+ 'field',
781
+ 'file',
782
+ 'finally',
783
+ 'get',
784
+ 'import',
785
+ 'init',
786
+ 'param',
787
+ 'property',
788
+ 'receiver',
789
+ 'set',
790
+ 'setparam',
791
+ 'where',
792
+ 'actual',
793
+ 'abstract',
794
+ 'annotation',
795
+ 'companion',
796
+ 'const',
797
+ 'crossinline',
798
+ 'data',
799
+ 'enum',
800
+ 'expect',
801
+ 'external',
802
+ 'final',
803
+ 'infix',
804
+ 'inline',
805
+ 'inner',
806
+ 'internal',
807
+ 'lateinit',
808
+ 'noinline',
809
+ 'open',
810
+ 'operator',
811
+ 'out',
812
+ 'override',
813
+ 'private',
814
+ 'protected',
815
+ 'public',
816
+ 'reified',
817
+ 'sealed',
818
+ 'suspend',
819
+ 'tailrec',
820
+ 'vararg',
821
+ ]);
822
+ return keywords.has(name);
823
+ }
824
+
825
+ isSwiftKeyword(name) {
826
+ const keywords = new Set([
827
+ 'associatedtype',
828
+ 'class',
829
+ 'deinit',
830
+ 'enum',
831
+ 'extension',
832
+ 'fileprivate',
833
+ 'func',
834
+ 'import',
835
+ // 'init' is deliberately absent: initializers are reported as methods
836
+ 'inout',
837
+ 'internal',
838
+ 'let',
839
+ 'open',
840
+ 'operator',
841
+ 'private',
842
+ 'precedencegroup',
843
+ 'protocol',
844
+ 'public',
845
+ 'rethrows',
846
+ 'static',
847
+ 'struct',
848
+ 'subscript',
849
+ 'typealias',
850
+ 'var',
851
+ 'break',
852
+ 'case',
853
+ 'catch',
854
+ 'continue',
855
+ 'default',
856
+ 'defer',
857
+ 'do',
858
+ 'else',
859
+ 'fallthrough',
860
+ 'for',
861
+ 'guard',
862
+ 'if',
863
+ 'in',
864
+ 'repeat',
865
+ 'return',
866
+ 'throw',
867
+ 'switch',
868
+ 'where',
869
+ 'while',
870
+ 'as',
871
+ 'false',
872
+ 'is',
873
+ 'nil',
874
+ 'self',
875
+ 'Self',
876
+ 'super',
877
+ 'throws',
878
+ 'true',
879
+ 'try',
880
+ 'await',
881
+ 'async',
882
+ ]);
883
+ return keywords.has(name);
884
+ }
885
+
886
+ isScalaKeyword(name) {
887
+ const keywords = new Set([
888
+ 'abstract',
889
+ 'case',
890
+ 'catch',
891
+ 'class',
892
+ 'def',
893
+ 'do',
894
+ 'else',
895
+ 'extends',
896
+ 'false',
897
+ 'final',
898
+ 'finally',
899
+ 'for',
900
+ 'forSome',
901
+ 'if',
902
+ 'implicit',
903
+ 'import',
904
+ 'lazy',
905
+ 'macro',
906
+ 'match',
907
+ 'new',
908
+ 'null',
909
+ 'object',
910
+ 'override',
911
+ 'package',
912
+ 'private',
913
+ 'protected',
914
+ 'return',
915
+ 'sealed',
916
+ 'super',
917
+ 'this',
918
+ 'throw',
919
+ 'trait',
920
+ 'try',
921
+ 'true',
922
+ 'type',
923
+ 'val',
924
+ 'var',
925
+ 'while',
926
+ 'with',
927
+ 'yield',
928
+ 'apply',
929
+ 'unapply',
930
+ ]);
931
+ return keywords.has(name);
932
+ }
933
+
934
+ /**
935
+ * Source text of each method, for per-method token counts: from its declaration line to
936
+ * the brace that closes its body (indentation for Python, a matching `end` for Ruby).
937
+ * A body must open before the next method's declaration, so an expression-bodied method
938
+ * (`const f = (x) => x * 2`, `fun f() = 1`) keeps just its declaration.
939
+ * @param {string} content - File content
940
+ * @param {Array<{line: number}>} methods - Methods from extractMethods()
941
+ * @param {string} filePath - Used to pick the block rules of the language
942
+ * @returns {string[]} One entry per method, in the order given
943
+ */
944
+ extractMethodSources(content, methods, filePath) {
945
+ const lines = content.split('\n');
946
+ const ext = path.extname(filePath).toLowerCase();
947
+ const starts = [...new Set(methods.map((method) => method.line))].sort((a, b) => a - b);
948
+
949
+ return methods.map((method) => {
950
+ const start = method.line - 1;
951
+ const next = starts.find((line) => line > method.line);
952
+ const limit = next ? next - 1 : lines.length;
953
+ let end;
954
+ if (ext === '.py') {
955
+ end = this.findIndentedBlockEnd(lines, start);
956
+ } else if (ext === '.rb') {
957
+ end = this.findRubyBlockEnd(lines, start);
958
+ } else {
959
+ end = this.findBraceBlockEnd(lines, start, limit);
960
+ }
961
+ return lines.slice(start, end + 1).join('\n');
962
+ });
963
+ }
964
+
965
+ /**
966
+ * Index of the line holding the `}` that closes the first `{` opened at or after
967
+ * `start` (and before `limit`). Strings and comments are skipped so braces inside them
968
+ * do not count, nor do braces inside the parameter list (`f(options = {}) {`,
969
+ * `({ a }) => {`). Without a body, the declaration ends at the first `;` or line end.
970
+ */
971
+ findBraceBlockEnd(lines, start, limit) {
972
+ let depth = 0;
973
+ let opened = false;
974
+ let quote = null;
975
+ let blockComment = false;
976
+ let parens = 0;
977
+
978
+ for (let i = start; i < lines.length; i++) {
979
+ if (!opened && i >= limit) break;
980
+ const line = lines[i];
981
+
982
+ for (let j = 0; j < line.length; j++) {
983
+ const ch = line[j];
984
+ if (blockComment) {
985
+ if (ch === '*' && line[j + 1] === '/') {
986
+ blockComment = false;
987
+ j++;
988
+ }
989
+ } else if (quote) {
990
+ if (ch === '\\') j++;
991
+ else if (ch === quote) quote = null;
992
+ } else if (ch === '/' && line[j + 1] === '/') {
993
+ break;
994
+ } else if (ch === '/' && line[j + 1] === '*') {
995
+ blockComment = true;
996
+ j++;
997
+ } else if (ch === '"' || ch === "'" || ch === '`') {
998
+ quote = ch;
999
+ } else if (!opened && (ch === '(' || ch === ')')) {
1000
+ parens = Math.max(0, parens + (ch === '(' ? 1 : -1));
1001
+ } else if (parens > 0) {
1002
+ // Default values and destructuring in the parameters, not the body
1003
+ } else if (ch === '{') {
1004
+ depth++;
1005
+ opened = true;
1006
+ } else if (ch === '}') {
1007
+ depth--;
1008
+ if (opened && depth === 0) return i;
1009
+ } else if (ch === ';' && !opened) {
1010
+ return i;
1011
+ }
1012
+ }
400
1013
 
401
- isKotlinKeyword(name) {
402
- const keywords = new Set([
403
- 'as', 'break', 'class', 'continue', 'do', 'else', 'false', 'for',
404
- 'fun', 'if', 'in', 'interface', 'is', 'null', 'object', 'package',
405
- 'return', 'super', 'this', 'throw', 'true', 'try', 'typealias', 'typeof',
406
- 'val', 'var', 'when', 'while', 'by', 'catch', 'constructor', 'delegate',
407
- 'dynamic', 'field', 'file', 'finally', 'get', 'import', 'init', 'param',
408
- 'property', 'receiver', 'set', 'setparam', 'where', 'actual', 'abstract',
409
- 'annotation', 'companion', 'const', 'crossinline', 'data', 'enum',
410
- 'expect', 'external', 'final', 'infix', 'inline', 'inner', 'internal',
411
- 'lateinit', 'noinline', 'open', 'operator', 'out', 'override', 'private',
412
- 'protected', 'public', 'reified', 'sealed', 'suspend', 'tailrec', 'vararg'
413
- ]);
414
- return keywords.has(name);
1014
+ // Only template literals span lines; other quotes end with the line
1015
+ if (quote && quote !== '`') quote = null;
1016
+ // An arrow with an expression body ends on its line
1017
+ if (!opened && /=>\s*[^\s{]/.test(line)) return i;
415
1018
  }
416
1019
 
417
- isSwiftKeyword(name) {
418
- const keywords = new Set([
419
- 'associatedtype', 'class', 'deinit', 'enum', 'extension', 'fileprivate',
420
- 'func', 'import', 'init', 'inout', 'internal', 'let', 'open', 'operator',
421
- 'private', 'precedencegroup', 'protocol', 'public', 'rethrows', 'static',
422
- 'struct', 'subscript', 'typealias', 'var', 'break', 'case', 'catch',
423
- 'continue', 'default', 'defer', 'do', 'else', 'fallthrough', 'for',
424
- 'guard', 'if', 'in', 'repeat', 'return', 'throw', 'switch', 'where',
425
- 'while', 'as', 'false', 'is', 'nil', 'self', 'Self', 'super', 'throws',
426
- 'true', 'try', 'await', 'async'
427
- ]);
428
- return keywords.has(name);
1020
+ return opened ? lines.length - 1 : start;
1021
+ }
1022
+
1023
+ /**
1024
+ * Last line of a Python block: the lines after `start` that are blank or indented
1025
+ * deeper than the `def` line, without trailing blank lines.
1026
+ */
1027
+ findIndentedBlockEnd(lines, start) {
1028
+ const indent = (line) => line.length - line.trimStart().length;
1029
+ const base = indent(lines[start]);
1030
+ let end = start;
1031
+ for (let i = start + 1; i < lines.length; i++) {
1032
+ if (lines[i].trim() === '') continue;
1033
+ if (indent(lines[i]) <= base) break;
1034
+ end = i;
429
1035
  }
430
-
431
- isScalaKeyword(name) {
432
- const keywords = new Set([
433
- 'abstract', 'case', 'catch', 'class', 'def', 'do', 'else', 'extends',
434
- 'false', 'final', 'finally', 'for', 'forSome', 'if', 'implicit',
435
- 'import', 'lazy', 'macro', 'match', 'new', 'null', 'object', 'override',
436
- 'package', 'private', 'protected', 'return', 'sealed', 'super', 'this',
437
- 'throw', 'trait', 'try', 'true', 'type', 'val', 'var', 'while', 'with',
438
- 'yield', 'apply', 'unapply'
439
- ]);
440
- return keywords.has(name);
1036
+ return end;
1037
+ }
1038
+
1039
+ /**
1040
+ * Line of the `end` that closes a Ruby `def` (the first `end` at the same indentation),
1041
+ * or the `def` line itself for an endless method (`def name = value`).
1042
+ */
1043
+ findRubyBlockEnd(lines, start) {
1044
+ const base = lines[start].length - lines[start].trimStart().length;
1045
+ for (let i = start + 1; i < lines.length; i++) {
1046
+ const line = lines[i];
1047
+ if (line.trim() === '') continue;
1048
+ const indent = line.length - line.trimStart().length;
1049
+ if (indent < base) break;
1050
+ if (indent === base && /^end\b/.test(line.trim())) return i;
1051
+ if (indent === base && /^(def|class|module|private|public|protected)\b/.test(line.trim())) {
1052
+ break;
1053
+ }
441
1054
  }
442
-
443
- extractMethodContent(content, methodName) {
444
- const patterns = [
445
- new RegExp(`(function\\s+${methodName}\\s*\\([^)]*\\)\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\})`, 'g'),
446
- new RegExp(`(${methodName}\\s*:\\s*function\\s*\\([^)]*\\)\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\})`, 'g')
447
- ];
448
-
449
- for (const pattern of patterns) {
450
- const match = pattern.exec(content);
451
- if (match) return match[1];
452
- }
453
- return null;
1055
+ return start;
1056
+ }
1057
+
1058
+ extractMethodContent(content, methodName) {
1059
+ const patterns = [
1060
+ new RegExp(
1061
+ `(function\\s+${methodName}\\s*\\([^)]*\\)\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\})`,
1062
+ 'g'
1063
+ ),
1064
+ new RegExp(
1065
+ `(${methodName}\\s*:\\s*function\\s*\\([^)]*\\)\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\})`,
1066
+ 'g'
1067
+ ),
1068
+ ];
1069
+
1070
+ for (const pattern of patterns) {
1071
+ const match = pattern.exec(content);
1072
+ if (match) return match[1];
454
1073
  }
1074
+ return null;
1075
+ }
455
1076
  }
456
1077
 
457
1078
  export default MethodAnalyzer;