skrypt-ai 0.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/autofix/index.d.ts +46 -0
  4. package/dist/autofix/index.js +240 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/autofix.d.ts +2 -0
  8. package/dist/commands/autofix.js +143 -0
  9. package/dist/commands/generate.d.ts +2 -0
  10. package/dist/commands/generate.js +320 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +56 -0
  13. package/dist/commands/review-pr.d.ts +2 -0
  14. package/dist/commands/review-pr.js +117 -0
  15. package/dist/commands/watch.d.ts +2 -0
  16. package/dist/commands/watch.js +142 -0
  17. package/dist/config/index.d.ts +2 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/loader.d.ts +9 -0
  20. package/dist/config/loader.js +82 -0
  21. package/dist/config/types.d.ts +24 -0
  22. package/dist/config/types.js +34 -0
  23. package/dist/generator/generator.d.ts +15 -0
  24. package/dist/generator/generator.js +144 -0
  25. package/dist/generator/index.d.ts +4 -0
  26. package/dist/generator/index.js +4 -0
  27. package/dist/generator/organizer.d.ts +29 -0
  28. package/dist/generator/organizer.js +222 -0
  29. package/dist/generator/types.d.ts +83 -0
  30. package/dist/generator/types.js +1 -0
  31. package/dist/generator/writer.d.ts +28 -0
  32. package/dist/generator/writer.js +320 -0
  33. package/dist/github/pr-comments.d.ts +40 -0
  34. package/dist/github/pr-comments.js +308 -0
  35. package/dist/llm/anthropic-client.d.ts +16 -0
  36. package/dist/llm/anthropic-client.js +92 -0
  37. package/dist/llm/index.d.ts +53 -0
  38. package/dist/llm/index.js +400 -0
  39. package/dist/llm/llm.manual-test.d.ts +1 -0
  40. package/dist/llm/llm.manual-test.js +112 -0
  41. package/dist/llm/llm.mock-test.d.ts +4 -0
  42. package/dist/llm/llm.mock-test.js +79 -0
  43. package/dist/llm/openai-client.d.ts +17 -0
  44. package/dist/llm/openai-client.js +90 -0
  45. package/dist/llm/types.d.ts +60 -0
  46. package/dist/llm/types.js +20 -0
  47. package/dist/scanner/content-type.d.ts +39 -0
  48. package/dist/scanner/content-type.js +194 -0
  49. package/dist/scanner/content-type.test.d.ts +1 -0
  50. package/dist/scanner/content-type.test.js +231 -0
  51. package/dist/scanner/go.d.ts +20 -0
  52. package/dist/scanner/go.js +269 -0
  53. package/dist/scanner/index.d.ts +21 -0
  54. package/dist/scanner/index.js +137 -0
  55. package/dist/scanner/python.d.ts +6 -0
  56. package/dist/scanner/python.js +57 -0
  57. package/dist/scanner/python_parser.py +230 -0
  58. package/dist/scanner/rust.d.ts +23 -0
  59. package/dist/scanner/rust.js +304 -0
  60. package/dist/scanner/scanner.test.d.ts +1 -0
  61. package/dist/scanner/scanner.test.js +210 -0
  62. package/dist/scanner/types.d.ts +50 -0
  63. package/dist/scanner/types.js +1 -0
  64. package/dist/scanner/typescript.d.ts +34 -0
  65. package/dist/scanner/typescript.js +327 -0
  66. package/dist/scanner/typescript.manual-test.d.ts +1 -0
  67. package/dist/scanner/typescript.manual-test.js +112 -0
  68. package/dist/template/docs.json +32 -0
  69. package/dist/template/mdx-components.tsx +62 -0
  70. package/dist/template/next-env.d.ts +6 -0
  71. package/dist/template/next.config.mjs +17 -0
  72. package/dist/template/package.json +39 -0
  73. package/dist/template/postcss.config.mjs +5 -0
  74. package/dist/template/public/search-index.json +1 -0
  75. package/dist/template/scripts/build-search-index.mjs +120 -0
  76. package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
  77. package/dist/template/src/app/api/openapi/route.ts +48 -0
  78. package/dist/template/src/app/api/rate-limit/route.ts +84 -0
  79. package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
  80. package/dist/template/src/app/docs/layout.tsx +9 -0
  81. package/dist/template/src/app/docs/page.mdx +67 -0
  82. package/dist/template/src/app/error.tsx +63 -0
  83. package/dist/template/src/app/layout.tsx +71 -0
  84. package/dist/template/src/app/page.tsx +18 -0
  85. package/dist/template/src/app/reference/route.ts +36 -0
  86. package/dist/template/src/app/robots.ts +14 -0
  87. package/dist/template/src/app/sitemap.ts +64 -0
  88. package/dist/template/src/components/breadcrumbs.tsx +41 -0
  89. package/dist/template/src/components/copy-button.tsx +29 -0
  90. package/dist/template/src/components/docs-layout.tsx +35 -0
  91. package/dist/template/src/components/edit-link.tsx +39 -0
  92. package/dist/template/src/components/feedback.tsx +52 -0
  93. package/dist/template/src/components/header.tsx +66 -0
  94. package/dist/template/src/components/mdx/accordion.tsx +48 -0
  95. package/dist/template/src/components/mdx/api-badge.tsx +57 -0
  96. package/dist/template/src/components/mdx/callout.tsx +111 -0
  97. package/dist/template/src/components/mdx/card.tsx +62 -0
  98. package/dist/template/src/components/mdx/changelog.tsx +57 -0
  99. package/dist/template/src/components/mdx/code-block.tsx +42 -0
  100. package/dist/template/src/components/mdx/code-group.tsx +125 -0
  101. package/dist/template/src/components/mdx/code-playground.tsx +322 -0
  102. package/dist/template/src/components/mdx/go-playground.tsx +235 -0
  103. package/dist/template/src/components/mdx/heading.tsx +37 -0
  104. package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
  105. package/dist/template/src/components/mdx/index.tsx +15 -0
  106. package/dist/template/src/components/mdx/param-table.tsx +71 -0
  107. package/dist/template/src/components/mdx/python-playground.tsx +293 -0
  108. package/dist/template/src/components/mdx/steps.tsx +43 -0
  109. package/dist/template/src/components/mdx/tabs.tsx +81 -0
  110. package/dist/template/src/components/rate-limit-display.tsx +183 -0
  111. package/dist/template/src/components/search-dialog.tsx +178 -0
  112. package/dist/template/src/components/sidebar.tsx +129 -0
  113. package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
  114. package/dist/template/src/components/table-of-contents.tsx +84 -0
  115. package/dist/template/src/components/theme-toggle.tsx +46 -0
  116. package/dist/template/src/components/version-selector.tsx +61 -0
  117. package/dist/template/src/contexts/syntax-theme.tsx +52 -0
  118. package/dist/template/src/lib/highlight.ts +83 -0
  119. package/dist/template/src/lib/search-types.ts +37 -0
  120. package/dist/template/src/lib/search.ts +125 -0
  121. package/dist/template/src/lib/utils.ts +6 -0
  122. package/dist/template/src/styles/globals.css +152 -0
  123. package/dist/template/tsconfig.json +25 -0
  124. package/dist/template/tsconfig.tsbuildinfo +1 -0
  125. package/package.json +72 -0
@@ -0,0 +1,210 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { scanFile, scanDirectory } from './index.js';
3
+ import { join } from 'path';
4
+ const TESTDATA = join(process.cwd(), 'testdata');
5
+ describe('Scanner', () => {
6
+ describe('scanFile', () => {
7
+ describe('Python files', () => {
8
+ it('should scan Python file and extract elements', async () => {
9
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
10
+ expect(result.errors).toHaveLength(0);
11
+ expect(result.language).toBe('python');
12
+ expect(result.elements.length).toBeGreaterThan(0);
13
+ });
14
+ it('should find expected Python functions', async () => {
15
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
16
+ const names = result.elements.map(e => e.name);
17
+ expect(names).toContain('greet');
18
+ expect(names).toContain('fetch_data');
19
+ });
20
+ it('should find Python class and methods', async () => {
21
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
22
+ const names = result.elements.map(e => e.name);
23
+ expect(names).toContain('Calculator');
24
+ expect(names).toContain('add');
25
+ expect(names).toContain('multiply');
26
+ });
27
+ it('should exclude private Python elements', async () => {
28
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
29
+ const names = result.elements.map(e => e.name);
30
+ expect(names).not.toContain('_private_method');
31
+ expect(names).not.toContain('_private_function');
32
+ expect(names).not.toContain('_PrivateClass');
33
+ });
34
+ it('should detect async Python functions', async () => {
35
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
36
+ const fetchData = result.elements.find(e => e.name === 'fetch_data');
37
+ expect(fetchData).toBeDefined();
38
+ expect(fetchData?.isAsync).toBe(true);
39
+ });
40
+ it('should extract Python function parameters', async () => {
41
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
42
+ const greet = result.elements.find(e => e.name === 'greet');
43
+ expect(greet).toBeDefined();
44
+ expect(greet?.parameters).toHaveLength(2);
45
+ expect(greet?.parameters[0].name).toBe('name');
46
+ expect(greet?.parameters[0].type).toBe('str');
47
+ });
48
+ it('should extract Python docstrings', async () => {
49
+ const result = await scanFile(join(TESTDATA, 'sample.py'));
50
+ const greet = result.elements.find(e => e.name === 'greet');
51
+ expect(greet?.docstring).toContain('Generate a greeting');
52
+ });
53
+ });
54
+ describe('TypeScript files', () => {
55
+ it('should scan TypeScript file and extract elements', async () => {
56
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
57
+ expect(result.errors).toHaveLength(0);
58
+ expect(result.language).toBe('typescript');
59
+ expect(result.elements.length).toBeGreaterThan(0);
60
+ });
61
+ it('should find expected TypeScript functions', async () => {
62
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
63
+ const names = result.elements.map(e => e.name);
64
+ expect(names).toContain('greet');
65
+ expect(names).toContain('fetchData');
66
+ expect(names).toContain('processItems');
67
+ });
68
+ it('should find TypeScript class and methods', async () => {
69
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
70
+ const names = result.elements.map(e => e.name);
71
+ expect(names).toContain('Calculator');
72
+ expect(names).toContain('add');
73
+ expect(names).toContain('multiply');
74
+ });
75
+ it('should exclude non-exported TypeScript elements', async () => {
76
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
77
+ const names = result.elements.map(e => e.name);
78
+ expect(names).not.toContain('internalHelper');
79
+ expect(names).not.toContain('_privateFunction');
80
+ });
81
+ it('should detect async TypeScript functions', async () => {
82
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
83
+ const fetchData = result.elements.find(e => e.name === 'fetchData');
84
+ const processItems = result.elements.find(e => e.name === 'processItems');
85
+ expect(fetchData?.isAsync).toBe(true);
86
+ expect(processItems?.isAsync).toBe(true);
87
+ });
88
+ it('should set parentClass for methods', async () => {
89
+ const result = await scanFile(join(TESTDATA, 'sample.ts'));
90
+ const addMethod = result.elements.find(e => e.name === 'add');
91
+ expect(addMethod?.parentClass).toBe('Calculator');
92
+ expect(addMethod?.kind).toBe('method');
93
+ });
94
+ });
95
+ describe('Go files', () => {
96
+ it('should scan Go file and extract elements', async () => {
97
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
98
+ expect(result.errors).toHaveLength(0);
99
+ expect(result.language).toBe('go');
100
+ expect(result.elements.length).toBeGreaterThan(0);
101
+ });
102
+ it('should find exported Go functions', async () => {
103
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
104
+ const names = result.elements.map(e => e.name);
105
+ expect(names).toContain('Greet');
106
+ expect(names).toContain('FetchData');
107
+ expect(names).toContain('NewCalculator');
108
+ expect(names).toContain('ProcessItems');
109
+ });
110
+ it('should find Go types', async () => {
111
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
112
+ const names = result.elements.map(e => e.name);
113
+ expect(names).toContain('Config');
114
+ expect(names).toContain('Calculator');
115
+ expect(names).toContain('Client');
116
+ });
117
+ it('should find Go methods', async () => {
118
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
119
+ const names = result.elements.map(e => e.name);
120
+ expect(names).toContain('Add');
121
+ expect(names).toContain('Multiply');
122
+ });
123
+ it('should exclude non-exported Go functions', async () => {
124
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
125
+ const names = result.elements.map(e => e.name);
126
+ expect(names).not.toContain('privateFunction');
127
+ expect(names).not.toContain('privateMethod');
128
+ });
129
+ it('should extract Go doc comments', async () => {
130
+ const result = await scanFile(join(TESTDATA, 'sample.go'));
131
+ const greet = result.elements.find(e => e.name === 'Greet');
132
+ expect(greet?.docstring).toContain('generates a greeting');
133
+ });
134
+ });
135
+ describe('Rust files', () => {
136
+ it('should scan Rust file and extract elements', async () => {
137
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
138
+ expect(result.errors).toHaveLength(0);
139
+ expect(result.language).toBe('rust');
140
+ expect(result.elements.length).toBeGreaterThan(0);
141
+ });
142
+ it('should find pub Rust functions', async () => {
143
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
144
+ const names = result.elements.map(e => e.name);
145
+ expect(names).toContain('greet');
146
+ expect(names).toContain('fetch_data');
147
+ expect(names).toContain('process_items');
148
+ });
149
+ it('should find pub Rust types', async () => {
150
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
151
+ const names = result.elements.map(e => e.name);
152
+ expect(names).toContain('Config');
153
+ expect(names).toContain('Calculator');
154
+ expect(names).toContain('Client');
155
+ });
156
+ it('should find pub impl methods', async () => {
157
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
158
+ const names = result.elements.map(e => e.name);
159
+ expect(names).toContain('new');
160
+ expect(names).toContain('add');
161
+ expect(names).toContain('multiply');
162
+ });
163
+ it('should exclude non-pub Rust functions', async () => {
164
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
165
+ const names = result.elements.map(e => e.name);
166
+ expect(names).not.toContain('private_function');
167
+ expect(names).not.toContain('private_method');
168
+ });
169
+ it('should detect async Rust functions', async () => {
170
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
171
+ const fetchData = result.elements.find(e => e.name === 'fetch_data');
172
+ expect(fetchData?.isAsync).toBe(true);
173
+ });
174
+ it('should extract Rust doc comments', async () => {
175
+ const result = await scanFile(join(TESTDATA, 'sample.rs'));
176
+ const greet = result.elements.find(e => e.name === 'greet');
177
+ expect(greet?.docstring).toContain('Generate a greeting');
178
+ });
179
+ });
180
+ });
181
+ describe('scanDirectory', () => {
182
+ it('should scan multiple files in directory', async () => {
183
+ const result = await scanDirectory(TESTDATA, {
184
+ include: ['**/*.py', '**/*.ts', '**/*.go', '**/*.rs'],
185
+ exclude: [],
186
+ });
187
+ expect(result.files.length).toBeGreaterThanOrEqual(4);
188
+ expect(result.totalElements).toBeGreaterThan(0);
189
+ expect(result.errors).toHaveLength(0);
190
+ });
191
+ it('should respect include patterns', async () => {
192
+ const result = await scanDirectory(TESTDATA, {
193
+ include: ['**/*.py'],
194
+ exclude: [],
195
+ });
196
+ expect(result.files.every(f => f.language === 'python')).toBe(true);
197
+ });
198
+ it('should have files from all languages', async () => {
199
+ const result = await scanDirectory(TESTDATA, {
200
+ include: ['**/*.py', '**/*.ts', '**/*.go', '**/*.rs'],
201
+ exclude: [],
202
+ });
203
+ const languages = new Set(result.files.map(f => f.language));
204
+ expect(languages.has('python')).toBe(true);
205
+ expect(languages.has('typescript')).toBe(true);
206
+ expect(languages.has('go')).toBe(true);
207
+ expect(languages.has('rust')).toBe(true);
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Represents a parameter in a function/method signature
3
+ */
4
+ export interface Parameter {
5
+ name: string;
6
+ type?: string;
7
+ default?: string;
8
+ description?: string;
9
+ }
10
+ /**
11
+ * Represents an extracted API element (function, class, method)
12
+ */
13
+ export interface APIElement {
14
+ kind: 'function' | 'class' | 'method';
15
+ name: string;
16
+ signature: string;
17
+ parameters: Parameter[];
18
+ returnType?: string;
19
+ docstring?: string;
20
+ filePath: string;
21
+ lineNumber: number;
22
+ parentClass?: string;
23
+ isAsync?: boolean;
24
+ isExported?: boolean;
25
+ isPublic?: boolean;
26
+ imports?: string[];
27
+ sourceContext?: string;
28
+ packageName?: string;
29
+ relatedTypes?: string[];
30
+ }
31
+ /**
32
+ * Result of scanning a file
33
+ */
34
+ export interface ScanResult {
35
+ filePath: string;
36
+ language: 'python' | 'javascript' | 'typescript' | 'go' | 'rust';
37
+ elements: APIElement[];
38
+ errors: string[];
39
+ }
40
+ /**
41
+ * Scanner interface - each language implements this
42
+ */
43
+ export interface Scanner {
44
+ /** Languages this scanner supports */
45
+ languages: string[];
46
+ /** Scan a single file and extract API elements */
47
+ scanFile(filePath: string): Promise<ScanResult>;
48
+ /** Check if this scanner can handle the given file */
49
+ canHandle(filePath: string): boolean;
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ export declare class TypeScriptScanner implements Scanner {
3
+ languages: string[];
4
+ canHandle(filePath: string): boolean;
5
+ scanFile(filePath: string): Promise<ScanResult>;
6
+ /**
7
+ * Extract all import statements from the file
8
+ */
9
+ private extractImports;
10
+ /**
11
+ * Infer package name from file path
12
+ */
13
+ private inferPackageName;
14
+ /**
15
+ * Get source context around a node (few lines before/after)
16
+ */
17
+ private getSourceContext;
18
+ private visit;
19
+ private isPrivate;
20
+ private isPrivateMember;
21
+ private isExported;
22
+ private getPropertyName;
23
+ private extractFunction;
24
+ private extractArrowFunction;
25
+ private extractClass;
26
+ private extractMethod;
27
+ private extractConstructor;
28
+ private extractParameters;
29
+ private buildSignature;
30
+ private buildArrowSignature;
31
+ private buildMethodSignature;
32
+ private buildConstructorSignature;
33
+ private getJSDoc;
34
+ }
@@ -0,0 +1,327 @@
1
+ import ts from 'typescript';
2
+ import { readFileSync } from 'fs';
3
+ export class TypeScriptScanner {
4
+ languages = ['typescript', 'javascript'];
5
+ canHandle(filePath) {
6
+ return /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) && !filePath.includes('.d.ts');
7
+ }
8
+ async scanFile(filePath) {
9
+ const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx')
10
+ ? 'typescript'
11
+ : 'javascript';
12
+ try {
13
+ const source = readFileSync(filePath, 'utf-8');
14
+ const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.jsx')
15
+ ? ts.ScriptKind.TSX
16
+ : ts.ScriptKind.TS);
17
+ const elements = [];
18
+ const errors = [];
19
+ // Extract imports for context
20
+ const imports = this.extractImports(sourceFile);
21
+ const packageName = this.inferPackageName(filePath);
22
+ this.visit(sourceFile, sourceFile, elements, filePath, source, imports, packageName);
23
+ return {
24
+ filePath,
25
+ language,
26
+ elements,
27
+ errors
28
+ };
29
+ }
30
+ catch (err) {
31
+ return {
32
+ filePath,
33
+ language,
34
+ elements: [],
35
+ errors: [`Failed to parse: ${err}`]
36
+ };
37
+ }
38
+ }
39
+ /**
40
+ * Extract all import statements from the file
41
+ */
42
+ extractImports(sourceFile) {
43
+ const imports = [];
44
+ ts.forEachChild(sourceFile, node => {
45
+ if (ts.isImportDeclaration(node)) {
46
+ imports.push(node.getText(sourceFile));
47
+ }
48
+ });
49
+ return imports;
50
+ }
51
+ /**
52
+ * Infer package name from file path
53
+ */
54
+ inferPackageName(filePath) {
55
+ // Try to find package.json and extract name
56
+ const parts = filePath.split('/');
57
+ for (let i = parts.length - 1; i >= 0; i--) {
58
+ if (parts[i] === 'src' && i > 0) {
59
+ // Likely package name is parent of src
60
+ return parts[i - 1];
61
+ }
62
+ if (parts[i] === 'packages' && i < parts.length - 1) {
63
+ return parts[i + 1];
64
+ }
65
+ }
66
+ // Fallback: use directory name
67
+ const srcIndex = filePath.lastIndexOf('/src/');
68
+ if (srcIndex > 0) {
69
+ const beforeSrc = filePath.slice(0, srcIndex);
70
+ return beforeSrc.split('/').pop() || 'unknown';
71
+ }
72
+ return 'unknown';
73
+ }
74
+ /**
75
+ * Get source context around a node (few lines before/after)
76
+ */
77
+ getSourceContext(source, lineNumber, contextLines = 3) {
78
+ const lines = source.split('\n');
79
+ const start = Math.max(0, lineNumber - contextLines - 1);
80
+ const end = Math.min(lines.length, lineNumber + contextLines);
81
+ return lines.slice(start, end).join('\n');
82
+ }
83
+ visit(node, sourceFile, elements, filePath, source, imports, packageName, parentClass) {
84
+ // Function declarations
85
+ if (ts.isFunctionDeclaration(node) && node.name) {
86
+ const name = node.name.text;
87
+ if (!this.isPrivate(name) && this.isExported(node)) {
88
+ const element = this.extractFunction(node, sourceFile, filePath);
89
+ element.imports = imports;
90
+ element.packageName = packageName;
91
+ element.sourceContext = this.getSourceContext(source, element.lineNumber, 5);
92
+ elements.push(element);
93
+ }
94
+ }
95
+ // Arrow functions assigned to const (exported)
96
+ if (ts.isVariableStatement(node) && this.isExported(node)) {
97
+ for (const decl of node.declarationList.declarations) {
98
+ if (ts.isIdentifier(decl.name) && decl.initializer) {
99
+ if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
100
+ const name = decl.name.text;
101
+ if (!this.isPrivate(name)) {
102
+ const element = this.extractArrowFunction(decl, decl.initializer, sourceFile, filePath);
103
+ element.imports = imports;
104
+ element.packageName = packageName;
105
+ element.sourceContext = this.getSourceContext(source, element.lineNumber, 5);
106
+ elements.push(element);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ // Class declarations
113
+ if (ts.isClassDeclaration(node) && node.name) {
114
+ const name = node.name.text;
115
+ if (!this.isPrivate(name) && this.isExported(node)) {
116
+ const classElement = this.extractClass(node, sourceFile, filePath);
117
+ classElement.imports = imports;
118
+ classElement.packageName = packageName;
119
+ classElement.sourceContext = this.getSourceContext(source, classElement.lineNumber, 10);
120
+ elements.push(classElement);
121
+ // Extract methods
122
+ for (const member of node.members) {
123
+ if (ts.isMethodDeclaration(member) && member.name) {
124
+ const methodName = this.getPropertyName(member.name);
125
+ if (methodName && !this.isPrivateMember(member) && !methodName.startsWith('_')) {
126
+ const methodElement = this.extractMethod(member, sourceFile, filePath, name);
127
+ methodElement.imports = imports;
128
+ methodElement.packageName = packageName;
129
+ methodElement.sourceContext = this.getSourceContext(source, methodElement.lineNumber, 5);
130
+ elements.push(methodElement);
131
+ }
132
+ }
133
+ if (ts.isConstructorDeclaration(member)) {
134
+ const ctorElement = this.extractConstructor(member, sourceFile, filePath, name);
135
+ ctorElement.imports = imports;
136
+ ctorElement.packageName = packageName;
137
+ ctorElement.sourceContext = this.getSourceContext(source, ctorElement.lineNumber, 5);
138
+ elements.push(ctorElement);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ // Recurse into children (but not into function/class bodies for top-level scan)
144
+ if (ts.isSourceFile(node) || ts.isModuleBlock(node)) {
145
+ ts.forEachChild(node, child => this.visit(child, sourceFile, elements, filePath, source, imports, packageName, parentClass));
146
+ }
147
+ }
148
+ isPrivate(name) {
149
+ return name.startsWith('_') && !name.startsWith('__');
150
+ }
151
+ isPrivateMember(node) {
152
+ const modifiers = ts.getModifiers(node);
153
+ if (modifiers) {
154
+ for (const mod of modifiers) {
155
+ if (mod.kind === ts.SyntaxKind.PrivateKeyword)
156
+ return true;
157
+ }
158
+ }
159
+ return false;
160
+ }
161
+ isExported(node) {
162
+ const modifiers = ts.getModifiers(node);
163
+ if (modifiers) {
164
+ for (const mod of modifiers) {
165
+ if (mod.kind === ts.SyntaxKind.ExportKeyword)
166
+ return true;
167
+ }
168
+ }
169
+ // Also check for default export or if it's a top-level declaration in a module
170
+ return false;
171
+ }
172
+ getPropertyName(name) {
173
+ if (ts.isIdentifier(name))
174
+ return name.text;
175
+ if (ts.isStringLiteral(name))
176
+ return name.text;
177
+ return undefined;
178
+ }
179
+ extractFunction(node, sourceFile, filePath) {
180
+ const name = node.name?.text ?? 'anonymous';
181
+ const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
182
+ return {
183
+ kind: 'function',
184
+ name,
185
+ signature: this.buildSignature(node, sourceFile),
186
+ parameters: this.extractParameters(node.parameters, sourceFile),
187
+ returnType: node.type ? node.type.getText(sourceFile) : undefined,
188
+ docstring: this.getJSDoc(node, sourceFile),
189
+ filePath,
190
+ lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
191
+ isAsync,
192
+ isExported: true,
193
+ isPublic: true
194
+ };
195
+ }
196
+ extractArrowFunction(decl, fn, sourceFile, filePath) {
197
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : 'anonymous';
198
+ const isAsync = fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
199
+ return {
200
+ kind: 'function',
201
+ name,
202
+ signature: this.buildArrowSignature(decl, fn, sourceFile),
203
+ parameters: this.extractParameters(fn.parameters, sourceFile),
204
+ returnType: fn.type ? fn.type.getText(sourceFile) : undefined,
205
+ docstring: this.getJSDoc(decl.parent.parent, sourceFile),
206
+ filePath,
207
+ lineNumber: sourceFile.getLineAndCharacterOfPosition(decl.getStart()).line + 1,
208
+ isAsync,
209
+ isExported: true,
210
+ isPublic: true
211
+ };
212
+ }
213
+ extractClass(node, sourceFile, filePath) {
214
+ const name = node.name?.text ?? 'AnonymousClass';
215
+ let signature = `class ${name}`;
216
+ if (node.heritageClauses) {
217
+ for (const clause of node.heritageClauses) {
218
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
219
+ signature += ` extends ${clause.types.map(t => t.getText(sourceFile)).join(', ')}`;
220
+ }
221
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
222
+ signature += ` implements ${clause.types.map(t => t.getText(sourceFile)).join(', ')}`;
223
+ }
224
+ }
225
+ }
226
+ return {
227
+ kind: 'class',
228
+ name,
229
+ signature,
230
+ parameters: [],
231
+ docstring: this.getJSDoc(node, sourceFile),
232
+ filePath,
233
+ lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
234
+ isExported: true,
235
+ isPublic: true
236
+ };
237
+ }
238
+ extractMethod(node, sourceFile, filePath, parentClass) {
239
+ const name = this.getPropertyName(node.name) ?? 'anonymous';
240
+ const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
241
+ return {
242
+ kind: 'method',
243
+ name,
244
+ signature: this.buildMethodSignature(node, sourceFile),
245
+ parameters: this.extractParameters(node.parameters, sourceFile),
246
+ returnType: node.type ? node.type.getText(sourceFile) : undefined,
247
+ docstring: this.getJSDoc(node, sourceFile),
248
+ filePath,
249
+ lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
250
+ parentClass,
251
+ isAsync,
252
+ isPublic: true
253
+ };
254
+ }
255
+ extractConstructor(node, sourceFile, filePath, parentClass) {
256
+ return {
257
+ kind: 'method',
258
+ name: 'constructor',
259
+ signature: this.buildConstructorSignature(node, sourceFile),
260
+ parameters: this.extractParameters(node.parameters, sourceFile),
261
+ docstring: this.getJSDoc(node, sourceFile),
262
+ filePath,
263
+ lineNumber: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
264
+ parentClass,
265
+ isPublic: true
266
+ };
267
+ }
268
+ extractParameters(params, sourceFile) {
269
+ return params.map(p => {
270
+ const name = ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile);
271
+ return {
272
+ name: p.dotDotDotToken ? `...${name}` : name,
273
+ type: p.type ? p.type.getText(sourceFile) : undefined,
274
+ default: p.initializer ? p.initializer.getText(sourceFile) : undefined
275
+ };
276
+ });
277
+ }
278
+ buildSignature(node, sourceFile) {
279
+ const async = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
280
+ const name = node.name.text;
281
+ const typeParams = node.typeParameters
282
+ ? `<${node.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
283
+ : '';
284
+ const params = node.parameters.map(p => p.getText(sourceFile)).join(', ');
285
+ const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
286
+ return `${async}function ${name}${typeParams}(${params})${returnType}`;
287
+ }
288
+ buildArrowSignature(decl, fn, sourceFile) {
289
+ const name = decl.name.text;
290
+ const async = fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
291
+ const typeParams = fn.typeParameters
292
+ ? `<${fn.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
293
+ : '';
294
+ const params = fn.parameters.map(p => p.getText(sourceFile)).join(', ');
295
+ const returnType = fn.type ? `: ${fn.type.getText(sourceFile)}` : '';
296
+ return `const ${name} = ${async}${typeParams}(${params})${returnType} => ...`;
297
+ }
298
+ buildMethodSignature(node, sourceFile) {
299
+ const async = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
300
+ const name = this.getPropertyName(node.name);
301
+ const typeParams = node.typeParameters
302
+ ? `<${node.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
303
+ : '';
304
+ const params = node.parameters.map(p => p.getText(sourceFile)).join(', ');
305
+ const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
306
+ return `${async}${name}${typeParams}(${params})${returnType}`;
307
+ }
308
+ buildConstructorSignature(node, sourceFile) {
309
+ const params = node.parameters.map(p => p.getText(sourceFile)).join(', ');
310
+ return `constructor(${params})`;
311
+ }
312
+ getJSDoc(node, sourceFile) {
313
+ // TypeScript's JSDoc is not in public types but exists at runtime
314
+ const jsDocNodes = node.jsDoc;
315
+ if (!jsDocNodes || jsDocNodes.length === 0)
316
+ return undefined;
317
+ const jsDoc = jsDocNodes[0];
318
+ if (jsDoc.comment) {
319
+ if (typeof jsDoc.comment === 'string') {
320
+ return jsDoc.comment;
321
+ }
322
+ // Handle JSDocComment[] for complex comments
323
+ return jsDoc.comment.map(c => c.getText(sourceFile)).join('');
324
+ }
325
+ return undefined;
326
+ }
327
+ }
@@ -0,0 +1 @@
1
+ export {};