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.
- package/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- 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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
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;
|