skrypt-ai 0.5.0 → 0.6.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/dist/auth/index.js +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +88 -6
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -102
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +24 -4
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +16 -2
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +84 -6
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +3 -2
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Scanner for Ruby source files
|
|
4
|
+
* Extracts: classes, modules, methods (public only)
|
|
5
|
+
*/
|
|
6
|
+
export class RubyScanner {
|
|
7
|
+
languages = ['ruby'];
|
|
8
|
+
canHandle(filePath) {
|
|
9
|
+
return (/\.rb$/.test(filePath) &&
|
|
10
|
+
!/_test\.rb$/.test(filePath) &&
|
|
11
|
+
!/_spec\.rb$/.test(filePath) &&
|
|
12
|
+
!/(^|\/)test\//.test(filePath) &&
|
|
13
|
+
!/(^|\/)spec\//.test(filePath));
|
|
14
|
+
}
|
|
15
|
+
async scanFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
18
|
+
const elements = [];
|
|
19
|
+
const errors = [];
|
|
20
|
+
const lines = source.split('\n');
|
|
21
|
+
// Extract module nesting as packageName
|
|
22
|
+
const packageName = this.extractModuleNesting(source);
|
|
23
|
+
// Extract imports (require / require_relative)
|
|
24
|
+
const imports = this.extractImports(source);
|
|
25
|
+
// Extract classes
|
|
26
|
+
this.extractClasses(source, lines, filePath, packageName, imports, elements);
|
|
27
|
+
// Extract modules (non-wrapping, constant-holder modules)
|
|
28
|
+
this.extractModules(source, lines, filePath, packageName, imports, elements);
|
|
29
|
+
// Extract methods
|
|
30
|
+
this.extractMethods(source, lines, filePath, packageName, imports, elements);
|
|
31
|
+
return {
|
|
32
|
+
filePath,
|
|
33
|
+
language: 'ruby',
|
|
34
|
+
elements,
|
|
35
|
+
errors
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
filePath,
|
|
41
|
+
language: 'ruby',
|
|
42
|
+
elements: [],
|
|
43
|
+
errors: [`Failed to parse: ${err}`]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Imports
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
extractImports(source) {
|
|
51
|
+
const imports = [];
|
|
52
|
+
const regex = /^(?:require|require_relative)\s+['"]([^'"]+)['"]/gm;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = regex.exec(source)) !== null) {
|
|
55
|
+
if (match[1])
|
|
56
|
+
imports.push(match[1]);
|
|
57
|
+
}
|
|
58
|
+
return imports;
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Module nesting → "App::Services"
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
extractModuleNesting(source) {
|
|
64
|
+
const modules = [];
|
|
65
|
+
const lines = source.split('\n');
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
const match = trimmed.match(/^module\s+(\w+)/);
|
|
69
|
+
if (match?.[1]) {
|
|
70
|
+
modules.push(match[1]);
|
|
71
|
+
}
|
|
72
|
+
// Stop at first non-module declaration (class, def, end, or constant)
|
|
73
|
+
if (/^(class|def|end)\s/.test(trimmed) || /^[A-Z]\w*\s*=/.test(trimmed))
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
return modules.length > 0 ? modules.join('::') : 'unknown';
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Classes
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
extractClasses(source, lines, filePath, packageName, imports, elements) {
|
|
82
|
+
const classRegex = /^\s*class\s+(\w+)(?:\s*<\s*(\w+))?/gm;
|
|
83
|
+
let match;
|
|
84
|
+
while ((match = classRegex.exec(source)) !== null) {
|
|
85
|
+
const name = match[1];
|
|
86
|
+
if (!name)
|
|
87
|
+
continue;
|
|
88
|
+
const superclass = match[2] ?? '';
|
|
89
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
90
|
+
const docstring = this.getDocComment(lines, lineNumber - 1);
|
|
91
|
+
const signature = superclass
|
|
92
|
+
? `class ${name} < ${superclass}`
|
|
93
|
+
: `class ${name}`;
|
|
94
|
+
elements.push({
|
|
95
|
+
kind: 'class',
|
|
96
|
+
name,
|
|
97
|
+
signature,
|
|
98
|
+
parameters: [],
|
|
99
|
+
docstring,
|
|
100
|
+
filePath,
|
|
101
|
+
lineNumber,
|
|
102
|
+
isExported: true,
|
|
103
|
+
isPublic: true,
|
|
104
|
+
imports,
|
|
105
|
+
packageName,
|
|
106
|
+
sourceContext: this.getSourceContext(lines, lineNumber, 15)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Modules (constant-holder / namespace modules that don't wrap classes)
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
extractModules(source, lines, filePath, packageName, imports, elements) {
|
|
114
|
+
const moduleRegex = /^\s*module\s+(\w+)/gm;
|
|
115
|
+
let match;
|
|
116
|
+
while ((match = moduleRegex.exec(source)) !== null) {
|
|
117
|
+
const name = match[1];
|
|
118
|
+
if (!name)
|
|
119
|
+
continue;
|
|
120
|
+
// Only emit modules that contain constants (FOO = ...) and do NOT
|
|
121
|
+
// contain class/def definitions — i.e. "leaf" modules.
|
|
122
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
123
|
+
const body = this.getModuleBody(source, match.index);
|
|
124
|
+
if (body === null)
|
|
125
|
+
continue;
|
|
126
|
+
// Skip modules that contain class or def declarations (wrapper modules)
|
|
127
|
+
if (/^\s*(?:class|def)\s+/m.test(body))
|
|
128
|
+
continue;
|
|
129
|
+
const docstring = this.getDocComment(lines, lineNumber - 1);
|
|
130
|
+
const signature = `module ${name}`;
|
|
131
|
+
elements.push({
|
|
132
|
+
kind: 'class', // Using 'class' for Ruby modules (same as Go types)
|
|
133
|
+
name,
|
|
134
|
+
signature,
|
|
135
|
+
parameters: [],
|
|
136
|
+
docstring,
|
|
137
|
+
filePath,
|
|
138
|
+
lineNumber,
|
|
139
|
+
isExported: true,
|
|
140
|
+
isPublic: true,
|
|
141
|
+
imports,
|
|
142
|
+
packageName,
|
|
143
|
+
sourceContext: this.getSourceContext(lines, lineNumber, 15)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the body of a module/class starting at the given source index.
|
|
149
|
+
* Returns the text between the opening line and its matching `end`, or null.
|
|
150
|
+
*/
|
|
151
|
+
getModuleBody(source, startIndex) {
|
|
152
|
+
const rest = source.slice(startIndex);
|
|
153
|
+
const firstNewline = rest.indexOf('\n');
|
|
154
|
+
if (firstNewline === -1)
|
|
155
|
+
return null;
|
|
156
|
+
let depth = 1;
|
|
157
|
+
const bodyLines = rest.slice(firstNewline + 1).split('\n');
|
|
158
|
+
const collected = [];
|
|
159
|
+
for (const line of bodyLines) {
|
|
160
|
+
const trimmed = line.trim();
|
|
161
|
+
// Count opens: class, module, def, do, begin, if/unless/while/for/case (as statement openers)
|
|
162
|
+
if (/^(?:class|module|def|begin|if|unless|while|for|case|do)\b/.test(trimmed) ||
|
|
163
|
+
/\bdo\s*(?:\|[^|]*\|)?\s*$/.test(trimmed)) {
|
|
164
|
+
depth++;
|
|
165
|
+
}
|
|
166
|
+
if (trimmed === 'end' || /^end\b/.test(trimmed)) {
|
|
167
|
+
depth--;
|
|
168
|
+
if (depth === 0)
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
collected.push(line);
|
|
172
|
+
}
|
|
173
|
+
return collected.join('\n');
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Methods
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
extractMethods(source, lines, filePath, packageName, imports, elements) {
|
|
179
|
+
// Build visibility map: for each line, is it under a `private` or `protected` marker?
|
|
180
|
+
const visibilityMap = this.buildVisibilityMap(lines);
|
|
181
|
+
// Match: def method_name(params) and def self.method_name(params)
|
|
182
|
+
const methodRegex = /^\s*def\s+(self\.)?(\w+[?!=]?)(?:\s*\(([^)]*)\))?/gm;
|
|
183
|
+
let match;
|
|
184
|
+
while ((match = methodRegex.exec(source)) !== null) {
|
|
185
|
+
const isSelfMethod = !!match[1];
|
|
186
|
+
const name = match[2];
|
|
187
|
+
const paramsStr = match[3] ?? '';
|
|
188
|
+
if (!name)
|
|
189
|
+
continue;
|
|
190
|
+
// Skip initialize (constructor internals)
|
|
191
|
+
if (name === 'initialize')
|
|
192
|
+
continue;
|
|
193
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
194
|
+
// Check visibility — skip private / protected
|
|
195
|
+
const visibility = visibilityMap.get(lineNumber);
|
|
196
|
+
if (visibility === 'private' || visibility === 'protected')
|
|
197
|
+
continue;
|
|
198
|
+
// Handle inline `private def method_name` / `protected def method_name`
|
|
199
|
+
const fullLine = lines[lineNumber - 1];
|
|
200
|
+
if (fullLine) {
|
|
201
|
+
const trimmedLine = fullLine.trim();
|
|
202
|
+
if (trimmedLine.startsWith('private ') || trimmedLine.startsWith('protected '))
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const docstring = this.getDocComment(lines, lineNumber - 1);
|
|
206
|
+
const parameters = this.parseRubyParams(paramsStr);
|
|
207
|
+
const parentClass = this.findParentClass(source, match.index);
|
|
208
|
+
const returnType = this.extractReturnType(lines, lineNumber - 1);
|
|
209
|
+
const sig = isSelfMethod
|
|
210
|
+
? `def self.${name}(${paramsStr})`
|
|
211
|
+
: `def ${name}(${paramsStr})`;
|
|
212
|
+
elements.push({
|
|
213
|
+
kind: parentClass ? 'method' : 'function',
|
|
214
|
+
name,
|
|
215
|
+
signature: sig,
|
|
216
|
+
parameters,
|
|
217
|
+
returnType,
|
|
218
|
+
docstring,
|
|
219
|
+
filePath,
|
|
220
|
+
lineNumber,
|
|
221
|
+
parentClass,
|
|
222
|
+
isExported: true,
|
|
223
|
+
isPublic: true,
|
|
224
|
+
imports,
|
|
225
|
+
packageName,
|
|
226
|
+
sourceContext: this.getSourceContext(lines, lineNumber)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Visibility tracking
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
/**
|
|
234
|
+
* Build a map of lineNumber → visibility for every `def` line.
|
|
235
|
+
* Ruby's `private` / `protected` markers apply to all subsequent defs
|
|
236
|
+
* within the same class/module scope until another visibility marker.
|
|
237
|
+
*/
|
|
238
|
+
buildVisibilityMap(lines) {
|
|
239
|
+
const map = new Map();
|
|
240
|
+
const scopeStack = [{ kind: 'module', visibility: 'public' }];
|
|
241
|
+
for (let i = 0; i < lines.length; i++) {
|
|
242
|
+
const line = lines[i];
|
|
243
|
+
if (!line)
|
|
244
|
+
continue;
|
|
245
|
+
const trimmed = line.trim();
|
|
246
|
+
// Opening a new class/module scope
|
|
247
|
+
if (/^class\s+/.test(trimmed) || /^module\s+/.test(trimmed)) {
|
|
248
|
+
scopeStack.push({ kind: 'class', visibility: 'public' });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
// Opening a def scope (we track it so `end` doesn't pop class/module)
|
|
252
|
+
if (/^def\s+/.test(trimmed)) {
|
|
253
|
+
// Record visibility before pushing def scope
|
|
254
|
+
const parentScope = scopeStack[scopeStack.length - 1];
|
|
255
|
+
map.set(i + 1, parentScope?.visibility ?? 'public');
|
|
256
|
+
scopeStack.push({ kind: 'def', visibility: parentScope?.visibility ?? 'public' });
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
// End of scope
|
|
260
|
+
if (trimmed === 'end') {
|
|
261
|
+
if (scopeStack.length > 1)
|
|
262
|
+
scopeStack.pop();
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
// Visibility marker (bare keyword on its own line)
|
|
266
|
+
if (/^(private|protected|public)\s*$/.test(trimmed)) {
|
|
267
|
+
const scope = scopeStack[scopeStack.length - 1];
|
|
268
|
+
if (scope && scope.kind !== 'def')
|
|
269
|
+
scope.visibility = trimmed;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return map;
|
|
274
|
+
}
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Parent class lookup
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
findParentClass(source, index) {
|
|
279
|
+
const before = source.slice(0, index);
|
|
280
|
+
const classMatches = [...before.matchAll(/class\s+(\w+)/g)];
|
|
281
|
+
if (classMatches.length === 0)
|
|
282
|
+
return undefined;
|
|
283
|
+
// Verify the class scope is still open (not yet closed by `end`)
|
|
284
|
+
const lastClassMatch = classMatches[classMatches.length - 1];
|
|
285
|
+
const className = lastClassMatch[1];
|
|
286
|
+
const afterClass = source.slice(lastClassMatch.index + lastClassMatch[0].length, index);
|
|
287
|
+
// Rough depth check: count class/module/def opens vs `end` closes
|
|
288
|
+
let depth = 1;
|
|
289
|
+
const scopeLines = afterClass.split('\n');
|
|
290
|
+
for (const scopeLine of scopeLines) {
|
|
291
|
+
const t = scopeLine.trim();
|
|
292
|
+
if (/^(?:class|module|def)\s+/.test(t))
|
|
293
|
+
depth++;
|
|
294
|
+
if (t === 'end')
|
|
295
|
+
depth--;
|
|
296
|
+
if (depth <= 0)
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
return depth > 0 ? className : undefined;
|
|
300
|
+
}
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// Parameter parsing
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
parseRubyParams(paramsStr) {
|
|
305
|
+
if (!paramsStr.trim())
|
|
306
|
+
return [];
|
|
307
|
+
const params = [];
|
|
308
|
+
const parts = this.splitParams(paramsStr);
|
|
309
|
+
for (const part of parts) {
|
|
310
|
+
const trimmed = part.trim();
|
|
311
|
+
if (!trimmed)
|
|
312
|
+
continue;
|
|
313
|
+
// Block param: &block
|
|
314
|
+
if (trimmed.startsWith('&')) {
|
|
315
|
+
params.push({ name: trimmed.slice(1), type: 'block' });
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Double-splat: **kwargs
|
|
319
|
+
if (trimmed.startsWith('**')) {
|
|
320
|
+
params.push({ name: trimmed.slice(2), type: '**kwargs' });
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// Splat: *args
|
|
324
|
+
if (trimmed.startsWith('*')) {
|
|
325
|
+
params.push({ name: trimmed.slice(1), type: '*args' });
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
// Keyword arg with default: name: default
|
|
329
|
+
const kwDefault = trimmed.match(/^(\w+):\s*(.+)$/);
|
|
330
|
+
if (kwDefault?.[1] && kwDefault[2] !== undefined) {
|
|
331
|
+
params.push({ name: kwDefault[1], type: 'keyword', default: kwDefault[2] });
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
// Keyword arg (required): name:
|
|
335
|
+
const kwRequired = trimmed.match(/^(\w+):$/);
|
|
336
|
+
if (kwRequired?.[1]) {
|
|
337
|
+
params.push({ name: kwRequired[1], type: 'keyword' });
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
// Positional with default: name = value
|
|
341
|
+
const posDefault = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
342
|
+
if (posDefault?.[1] && posDefault[2] !== undefined) {
|
|
343
|
+
params.push({ name: posDefault[1], default: posDefault[2] });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// Plain positional
|
|
347
|
+
params.push({ name: trimmed });
|
|
348
|
+
}
|
|
349
|
+
return params;
|
|
350
|
+
}
|
|
351
|
+
splitParams(str) {
|
|
352
|
+
const parts = [];
|
|
353
|
+
let depth = 0;
|
|
354
|
+
let current = '';
|
|
355
|
+
for (const char of str) {
|
|
356
|
+
if (char === '(' || char === '[' || char === '{')
|
|
357
|
+
depth++;
|
|
358
|
+
else if (char === ')' || char === ']' || char === '}')
|
|
359
|
+
depth--;
|
|
360
|
+
else if (char === ',' && depth === 0) {
|
|
361
|
+
parts.push(current);
|
|
362
|
+
current = '';
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
current += char;
|
|
366
|
+
}
|
|
367
|
+
if (current.trim())
|
|
368
|
+
parts.push(current);
|
|
369
|
+
return parts;
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Doc comments (YARD and plain #)
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
getDocComment(lines, lineIndex) {
|
|
375
|
+
const comments = [];
|
|
376
|
+
let i = lineIndex - 1;
|
|
377
|
+
while (i >= 0) {
|
|
378
|
+
const lineContent = lines[i];
|
|
379
|
+
if (!lineContent)
|
|
380
|
+
break;
|
|
381
|
+
const line = lineContent.trim();
|
|
382
|
+
if (line.startsWith('#')) {
|
|
383
|
+
comments.unshift(line.replace(/^#\s?/, ''));
|
|
384
|
+
i--;
|
|
385
|
+
}
|
|
386
|
+
else if (line === '') {
|
|
387
|
+
i--;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return comments.length > 0 ? comments.join('\n') : undefined;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Extract @return type from YARD comments above a method.
|
|
397
|
+
*/
|
|
398
|
+
extractReturnType(lines, lineIndex) {
|
|
399
|
+
let i = lineIndex - 1;
|
|
400
|
+
while (i >= 0) {
|
|
401
|
+
const lineContent = lines[i];
|
|
402
|
+
if (!lineContent)
|
|
403
|
+
break;
|
|
404
|
+
const line = lineContent.trim();
|
|
405
|
+
if (line.startsWith('#')) {
|
|
406
|
+
const returnMatch = line.match(/@return\s+\[([^\]]+)\]/);
|
|
407
|
+
if (returnMatch?.[1])
|
|
408
|
+
return returnMatch[1];
|
|
409
|
+
i--;
|
|
410
|
+
}
|
|
411
|
+
else if (line === '') {
|
|
412
|
+
i--;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// Helpers
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
getLineNumber(source, index) {
|
|
424
|
+
return source.slice(0, index).split('\n').length;
|
|
425
|
+
}
|
|
426
|
+
getSourceContext(lines, lineNumber, context = 5) {
|
|
427
|
+
const start = Math.max(0, lineNumber - context - 1);
|
|
428
|
+
const end = Math.min(lines.length, lineNumber + context);
|
|
429
|
+
return lines.slice(start, end).join('\n');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Scanner, ScanResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scanner for Swift source files
|
|
4
|
+
* Extracts: public/open classes, structs, enums, protocols, actors, functions, methods
|
|
5
|
+
*/
|
|
6
|
+
export declare class SwiftScanner implements Scanner {
|
|
7
|
+
languages: string[];
|
|
8
|
+
canHandle(filePath: string): boolean;
|
|
9
|
+
scanFile(filePath: string): Promise<ScanResult>;
|
|
10
|
+
private inferModuleName;
|
|
11
|
+
private extractImports;
|
|
12
|
+
private extractTypes;
|
|
13
|
+
private extractParenContent;
|
|
14
|
+
private extractTrailingSignature;
|
|
15
|
+
private extractTopLevelFunctions;
|
|
16
|
+
private extractMethods;
|
|
17
|
+
private buildSignature;
|
|
18
|
+
private extractBlock;
|
|
19
|
+
private parseSwiftParams;
|
|
20
|
+
private findDefaultValueSplit;
|
|
21
|
+
private splitParams;
|
|
22
|
+
private getLineNumber;
|
|
23
|
+
private getDocComment;
|
|
24
|
+
private getSourceContext;
|
|
25
|
+
}
|