codebase-context 1.0.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/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/analyzers/angular/index.d.ts +44 -0
- package/dist/analyzers/angular/index.d.ts.map +1 -0
- package/dist/analyzers/angular/index.js +922 -0
- package/dist/analyzers/angular/index.js.map +1 -0
- package/dist/analyzers/generic/index.d.ts +23 -0
- package/dist/analyzers/generic/index.d.ts.map +1 -0
- package/dist/analyzers/generic/index.js +354 -0
- package/dist/analyzers/generic/index.js.map +1 -0
- package/dist/core/analyzer-registry.d.ts +36 -0
- package/dist/core/analyzer-registry.d.ts.map +1 -0
- package/dist/core/analyzer-registry.js +78 -0
- package/dist/core/analyzer-registry.js.map +1 -0
- package/dist/core/file-watcher.d.ts +63 -0
- package/dist/core/file-watcher.d.ts.map +1 -0
- package/dist/core/file-watcher.js +210 -0
- package/dist/core/file-watcher.js.map +1 -0
- package/dist/core/indexer.d.ts +29 -0
- package/dist/core/indexer.d.ts.map +1 -0
- package/dist/core/indexer.js +507 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/search.d.ts +31 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +307 -0
- package/dist/core/search.js.map +1 -0
- package/dist/embeddings/index.d.ts +5 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +33 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/openai.d.ts +19 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +59 -0
- package/dist/embeddings/openai.js.map +1 -0
- package/dist/embeddings/transformers.d.ts +17 -0
- package/dist/embeddings/transformers.d.ts.map +1 -0
- package/dist/embeddings/transformers.js +83 -0
- package/dist/embeddings/transformers.js.map +1 -0
- package/dist/embeddings/types.d.ts +20 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +9 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +790 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +58 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +81 -0
- package/dist/lib.js.map +1 -0
- package/dist/storage/index.d.ts +12 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +18 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/lancedb.d.ts +24 -0
- package/dist/storage/lancedb.d.ts.map +1 -0
- package/dist/storage/lancedb.js +197 -0
- package/dist/storage/lancedb.js.map +1 -0
- package/dist/storage/types.d.ts +45 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +8 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/types/index.d.ts +367 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/chunking.d.ts +23 -0
- package/dist/utils/chunking.d.ts.map +1 -0
- package/dist/utils/chunking.js +226 -0
- package/dist/utils/chunking.js.map +1 -0
- package/dist/utils/language-detection.d.ts +29 -0
- package/dist/utils/language-detection.d.ts.map +1 -0
- package/dist/utils/language-detection.js +127 -0
- package/dist/utils/language-detection.js.map +1 -0
- package/dist/utils/pattern-detector.d.ts +41 -0
- package/dist/utils/pattern-detector.d.ts.map +1 -0
- package/dist/utils/pattern-detector.js +101 -0
- package/dist/utils/pattern-detector.js.map +1 -0
- package/dist/utils/usage-tracker.d.ts +120 -0
- package/dist/utils/usage-tracker.d.ts.map +1 -0
- package/dist/utils/usage-tracker.js +336 -0
- package/dist/utils/usage-tracker.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular Analyzer - Comprehensive Angular-specific code analysis
|
|
3
|
+
* Understands components, services, directives, pipes, modules, guards, interceptors, etc.
|
|
4
|
+
* Detects state management patterns, architectural layers, and Angular-specific patterns
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { parse } from "@typescript-eslint/typescript-estree";
|
|
9
|
+
import { createChunksFromCode } from "../../utils/chunking.js";
|
|
10
|
+
export class AngularAnalyzer {
|
|
11
|
+
name = "angular";
|
|
12
|
+
version = "1.0.0";
|
|
13
|
+
supportedExtensions = [
|
|
14
|
+
".ts",
|
|
15
|
+
".js",
|
|
16
|
+
".html",
|
|
17
|
+
".scss",
|
|
18
|
+
".css",
|
|
19
|
+
".sass",
|
|
20
|
+
".less",
|
|
21
|
+
];
|
|
22
|
+
priority = 100; // Highest priority for Angular files
|
|
23
|
+
angularPatterns = {
|
|
24
|
+
component: /@Component\s*\(/,
|
|
25
|
+
service: /@Injectable\s*\(/,
|
|
26
|
+
directive: /@Directive\s*\(/,
|
|
27
|
+
pipe: /@Pipe\s*\(/,
|
|
28
|
+
module: /@NgModule\s*\(/,
|
|
29
|
+
// Guards: Check for interface implementation OR method signature OR functional guard
|
|
30
|
+
guard: /(?:implements\s+(?:CanActivate|CanDeactivate|CanLoad|CanMatch)|canActivate\s*\(|canDeactivate\s*\(|canLoad\s*\(|canMatch\s*\(|CanActivateFn|CanDeactivateFn|CanMatchFn)/,
|
|
31
|
+
interceptor: /(?:implements\s+HttpInterceptor|intercept\s*\(|HttpInterceptorFn)/,
|
|
32
|
+
resolver: /(?:implements\s+Resolve|resolve\s*\(|ResolveFn)/,
|
|
33
|
+
validator: /(?:implements\s+(?:Validator|AsyncValidator)|validate\s*\()/,
|
|
34
|
+
};
|
|
35
|
+
stateManagementPatterns = {
|
|
36
|
+
ngrx: /@ngrx\/store|createAction|createReducer|createSelector/,
|
|
37
|
+
akita: /@datorama\/akita|Query|Store\.update/,
|
|
38
|
+
elf: /@ngneat\/elf|createStore|withEntities/,
|
|
39
|
+
signals: /\bsignal\s*[<(]|\bcomputed\s*[<(]|\beffect\s*\(|\blinkedSignal\s*[<(]/,
|
|
40
|
+
rxjsState: /BehaviorSubject|ReplaySubject|shareReplay/,
|
|
41
|
+
};
|
|
42
|
+
modernAngularPatterns = {
|
|
43
|
+
signalInput: /\binput\s*[<(]|\binput\.required\s*[<(]/,
|
|
44
|
+
signalOutput: /\boutput\s*[<(]/,
|
|
45
|
+
signalModel: /\bmodel\s*[<(]|\bmodel\.required\s*[<(]/,
|
|
46
|
+
signalViewChild: /\bviewChild\s*[<(]|\bviewChild\.required\s*[<(]/,
|
|
47
|
+
signalViewChildren: /\bviewChildren\s*[<(]/,
|
|
48
|
+
signalContentChild: /\bcontentChild\s*[<(]|\bcontentChild\.required\s*[<(]/,
|
|
49
|
+
signalContentChildren: /\bcontentChildren\s*[<(]/,
|
|
50
|
+
controlFlowIf: /@if\s*\(/,
|
|
51
|
+
controlFlowFor: /@for\s*\(/,
|
|
52
|
+
controlFlowSwitch: /@switch\s*\(/,
|
|
53
|
+
controlFlowDefer: /@defer\s*[({]/,
|
|
54
|
+
injectFunction: /\binject\s*[<(]/,
|
|
55
|
+
};
|
|
56
|
+
canAnalyze(filePath, content) {
|
|
57
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
58
|
+
if (!this.supportedExtensions.includes(ext)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// For TypeScript files, check if it contains Angular decorators
|
|
62
|
+
if (ext === ".ts" && content) {
|
|
63
|
+
return Object.values(this.angularPatterns).some((pattern) => pattern.test(content));
|
|
64
|
+
}
|
|
65
|
+
// Angular component templates and styles
|
|
66
|
+
if ([".html", ".scss", ".css", ".sass", ".less"].includes(ext)) {
|
|
67
|
+
// Check if there's a corresponding .ts file
|
|
68
|
+
const baseName = filePath.replace(/\.(html|scss|css|sass|less)$/, "");
|
|
69
|
+
return true; // We'll verify during analysis
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
async analyze(filePath, content) {
|
|
74
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
75
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
76
|
+
if (ext === ".ts") {
|
|
77
|
+
return this.analyzeTypeScriptFile(filePath, content, relativePath);
|
|
78
|
+
}
|
|
79
|
+
else if (ext === ".html") {
|
|
80
|
+
return this.analyzeTemplateFile(filePath, content, relativePath);
|
|
81
|
+
}
|
|
82
|
+
else if ([".scss", ".css", ".sass", ".less"].includes(ext)) {
|
|
83
|
+
return this.analyzeStyleFile(filePath, content, relativePath);
|
|
84
|
+
}
|
|
85
|
+
// Fallback
|
|
86
|
+
return {
|
|
87
|
+
filePath,
|
|
88
|
+
language: "unknown",
|
|
89
|
+
framework: "angular",
|
|
90
|
+
components: [],
|
|
91
|
+
imports: [],
|
|
92
|
+
exports: [],
|
|
93
|
+
dependencies: [],
|
|
94
|
+
metadata: {},
|
|
95
|
+
chunks: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async analyzeTypeScriptFile(filePath, content, relativePath) {
|
|
99
|
+
const components = [];
|
|
100
|
+
const imports = [];
|
|
101
|
+
const exports = [];
|
|
102
|
+
const dependencies = [];
|
|
103
|
+
try {
|
|
104
|
+
const ast = parse(content, {
|
|
105
|
+
loc: true,
|
|
106
|
+
range: true,
|
|
107
|
+
comment: true,
|
|
108
|
+
});
|
|
109
|
+
// Extract imports
|
|
110
|
+
for (const node of ast.body) {
|
|
111
|
+
if (node.type === "ImportDeclaration" && node.source.value) {
|
|
112
|
+
const source = node.source.value;
|
|
113
|
+
imports.push({
|
|
114
|
+
source,
|
|
115
|
+
imports: node.specifiers.map((s) => {
|
|
116
|
+
if (s.type === "ImportDefaultSpecifier")
|
|
117
|
+
return "default";
|
|
118
|
+
if (s.type === "ImportNamespaceSpecifier")
|
|
119
|
+
return "*";
|
|
120
|
+
return s.imported?.name || s.local.name;
|
|
121
|
+
}),
|
|
122
|
+
isDefault: node.specifiers.some((s) => s.type === "ImportDefaultSpecifier"),
|
|
123
|
+
isDynamic: false,
|
|
124
|
+
line: node.loc?.start.line,
|
|
125
|
+
});
|
|
126
|
+
// Track dependencies
|
|
127
|
+
if (!source.startsWith(".") && !source.startsWith("/")) {
|
|
128
|
+
dependencies.push(source.split("/")[0]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Extract class declarations with decorators
|
|
132
|
+
if (node.type === "ExportNamedDeclaration" &&
|
|
133
|
+
node.declaration?.type === "ClassDeclaration") {
|
|
134
|
+
const classNode = node.declaration;
|
|
135
|
+
if (classNode.id && classNode.decorators) {
|
|
136
|
+
const component = await this.extractAngularComponent(classNode, content);
|
|
137
|
+
if (component) {
|
|
138
|
+
components.push(component);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Handle direct class exports
|
|
143
|
+
if (node.type === "ClassDeclaration" && node.id && node.decorators) {
|
|
144
|
+
const component = await this.extractAngularComponent(node, content);
|
|
145
|
+
if (component) {
|
|
146
|
+
components.push(component);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Extract exports
|
|
150
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
151
|
+
if (node.declaration) {
|
|
152
|
+
if (node.declaration.type === "ClassDeclaration" &&
|
|
153
|
+
node.declaration.id) {
|
|
154
|
+
exports.push({
|
|
155
|
+
name: node.declaration.id.name,
|
|
156
|
+
isDefault: false,
|
|
157
|
+
type: "class",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (node.type === "ExportDefaultDeclaration") {
|
|
163
|
+
const name = node.declaration.type === "Identifier"
|
|
164
|
+
? node.declaration.name
|
|
165
|
+
: "default";
|
|
166
|
+
exports.push({
|
|
167
|
+
name,
|
|
168
|
+
isDefault: true,
|
|
169
|
+
type: "default",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.warn(`Failed to parse Angular TypeScript file ${filePath}:`, error);
|
|
176
|
+
}
|
|
177
|
+
// Detect state management
|
|
178
|
+
const statePattern = this.detectStateManagement(content);
|
|
179
|
+
// Detect Angular v17+ modern patterns
|
|
180
|
+
const modernPatterns = this.detectModernAngularPatterns(content);
|
|
181
|
+
// Determine architectural layer
|
|
182
|
+
const layer = this.determineLayer(filePath, components);
|
|
183
|
+
// Create chunks with Angular-specific metadata
|
|
184
|
+
const chunks = await createChunksFromCode(content, filePath, relativePath, "typescript", components, {
|
|
185
|
+
framework: "angular",
|
|
186
|
+
layer,
|
|
187
|
+
statePattern,
|
|
188
|
+
dependencies,
|
|
189
|
+
modernPatterns,
|
|
190
|
+
});
|
|
191
|
+
// Build detected patterns for the indexer to forward
|
|
192
|
+
const detectedPatterns = [];
|
|
193
|
+
// Dependency Injection pattern
|
|
194
|
+
if (modernPatterns.includes("injectFunction")) {
|
|
195
|
+
detectedPatterns.push({ category: "dependencyInjection", name: "inject() function" });
|
|
196
|
+
}
|
|
197
|
+
else if (content.includes("constructor(") && content.includes("private") &&
|
|
198
|
+
(relativePath.endsWith(".service.ts") || relativePath.endsWith(".component.ts"))) {
|
|
199
|
+
detectedPatterns.push({ category: "dependencyInjection", name: "Constructor injection" });
|
|
200
|
+
}
|
|
201
|
+
// State Management pattern
|
|
202
|
+
if (/BehaviorSubject|ReplaySubject|Subject|Observable/.test(content)) {
|
|
203
|
+
detectedPatterns.push({ category: "stateManagement", name: "RxJS" });
|
|
204
|
+
}
|
|
205
|
+
if (modernPatterns.some((p) => p.startsWith("signal"))) {
|
|
206
|
+
detectedPatterns.push({ category: "stateManagement", name: "Signals" });
|
|
207
|
+
}
|
|
208
|
+
// Reactivity patterns
|
|
209
|
+
if (/\beffect\s*\(/.test(content)) {
|
|
210
|
+
detectedPatterns.push({ category: "reactivity", name: "Effect" });
|
|
211
|
+
}
|
|
212
|
+
if (/\bcomputed\s*[<(]/.test(content)) {
|
|
213
|
+
detectedPatterns.push({ category: "reactivity", name: "Computed" });
|
|
214
|
+
}
|
|
215
|
+
// Component Style pattern detection
|
|
216
|
+
// Logic: explicit standalone: true → Standalone
|
|
217
|
+
// explicit standalone: false → NgModule-based
|
|
218
|
+
// no explicit flag + uses modern patterns (inject, signals) → likely Standalone (Angular v19+ default)
|
|
219
|
+
// no explicit flag + no modern patterns → ambiguous, don't classify
|
|
220
|
+
const hasExplicitStandalone = content.includes("standalone: true");
|
|
221
|
+
const hasExplicitNgModule = content.includes("standalone: false");
|
|
222
|
+
const usesModernPatterns = modernPatterns.includes("injectFunction") ||
|
|
223
|
+
modernPatterns.some(p => p.startsWith("signal"));
|
|
224
|
+
if (relativePath.endsWith("component.ts") || relativePath.endsWith("directive.ts") || relativePath.endsWith("pipe.ts")) {
|
|
225
|
+
if (hasExplicitStandalone) {
|
|
226
|
+
detectedPatterns.push({ category: "componentStyle", name: "Standalone" });
|
|
227
|
+
}
|
|
228
|
+
else if (hasExplicitNgModule) {
|
|
229
|
+
detectedPatterns.push({ category: "componentStyle", name: "NgModule-based" });
|
|
230
|
+
}
|
|
231
|
+
else if (usesModernPatterns) {
|
|
232
|
+
// No explicit flag but uses modern patterns → likely v19+ standalone default
|
|
233
|
+
detectedPatterns.push({ category: "componentStyle", name: "Standalone" });
|
|
234
|
+
}
|
|
235
|
+
// If no explicit flag and no modern patterns, don't classify (ambiguous)
|
|
236
|
+
}
|
|
237
|
+
// Input style pattern
|
|
238
|
+
if (modernPatterns.includes("signalInput")) {
|
|
239
|
+
detectedPatterns.push({ category: "componentInputs", name: "Signal-based inputs" });
|
|
240
|
+
}
|
|
241
|
+
else if (content.includes("@Input()")) {
|
|
242
|
+
detectedPatterns.push({ category: "componentInputs", name: "Decorator-based @Input" });
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
filePath,
|
|
246
|
+
language: "typescript",
|
|
247
|
+
framework: "angular",
|
|
248
|
+
components,
|
|
249
|
+
imports,
|
|
250
|
+
exports,
|
|
251
|
+
dependencies: dependencies.map((name) => ({
|
|
252
|
+
name,
|
|
253
|
+
category: this.categorizeDependency(name),
|
|
254
|
+
layer,
|
|
255
|
+
})),
|
|
256
|
+
metadata: {
|
|
257
|
+
analyzer: this.name,
|
|
258
|
+
layer,
|
|
259
|
+
statePattern,
|
|
260
|
+
modernPatterns,
|
|
261
|
+
// isStandalone: true if explicit standalone: true, or if uses modern patterns (implying v19+ default)
|
|
262
|
+
isStandalone: content.includes("standalone: true") ||
|
|
263
|
+
(!content.includes("standalone: false") &&
|
|
264
|
+
(modernPatterns.includes("injectFunction") || modernPatterns.some(p => p.startsWith("signal")))),
|
|
265
|
+
hasRoutes: content.includes("RouterModule") || content.includes("routes"),
|
|
266
|
+
usesSignals: modernPatterns.length > 0 &&
|
|
267
|
+
modernPatterns.some((p) => p.startsWith("signal")),
|
|
268
|
+
usesControlFlow: modernPatterns.some((p) => p.startsWith("controlFlow")),
|
|
269
|
+
usesInject: modernPatterns.includes("injectFunction"),
|
|
270
|
+
usesRxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/.test(content),
|
|
271
|
+
usesEffect: /\beffect\s*\(/.test(content),
|
|
272
|
+
usesComputed: /\bcomputed\s*[<(]/.test(content),
|
|
273
|
+
componentType: components.length > 0 ? components[0].metadata.angularType : undefined,
|
|
274
|
+
// NEW: Patterns for the indexer to forward generically
|
|
275
|
+
detectedPatterns,
|
|
276
|
+
},
|
|
277
|
+
chunks,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Detect Angular v17+ modern patterns in the code
|
|
282
|
+
*/
|
|
283
|
+
detectModernAngularPatterns(content) {
|
|
284
|
+
const detected = [];
|
|
285
|
+
for (const [patternName, regex] of Object.entries(this.modernAngularPatterns)) {
|
|
286
|
+
if (regex.test(content)) {
|
|
287
|
+
detected.push(patternName);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return detected;
|
|
291
|
+
}
|
|
292
|
+
async extractAngularComponent(classNode, content) {
|
|
293
|
+
if (!classNode.decorators || classNode.decorators.length === 0) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const decorator = classNode.decorators[0];
|
|
297
|
+
const decoratorName = decorator.expression.callee?.name || decorator.expression.name;
|
|
298
|
+
let componentType;
|
|
299
|
+
let angularType;
|
|
300
|
+
// Determine Angular component type
|
|
301
|
+
if (decoratorName === "Component") {
|
|
302
|
+
componentType = "component";
|
|
303
|
+
angularType = "component";
|
|
304
|
+
}
|
|
305
|
+
else if (decoratorName === "Directive") {
|
|
306
|
+
componentType = "directive";
|
|
307
|
+
angularType = "directive";
|
|
308
|
+
}
|
|
309
|
+
else if (decoratorName === "Pipe") {
|
|
310
|
+
componentType = "pipe";
|
|
311
|
+
angularType = "pipe";
|
|
312
|
+
}
|
|
313
|
+
else if (decoratorName === "NgModule") {
|
|
314
|
+
componentType = "module";
|
|
315
|
+
angularType = "module";
|
|
316
|
+
}
|
|
317
|
+
else if (decoratorName === "Injectable") {
|
|
318
|
+
// For @Injectable, check if it's actually a guard/interceptor/resolver/validator
|
|
319
|
+
// before defaulting to 'service'
|
|
320
|
+
const classContent = content.substring(classNode.range[0], classNode.range[1]);
|
|
321
|
+
if (this.angularPatterns.guard.test(classContent)) {
|
|
322
|
+
componentType = "guard";
|
|
323
|
+
angularType = "guard";
|
|
324
|
+
}
|
|
325
|
+
else if (this.angularPatterns.interceptor.test(classContent)) {
|
|
326
|
+
componentType = "interceptor";
|
|
327
|
+
angularType = "interceptor";
|
|
328
|
+
}
|
|
329
|
+
else if (this.angularPatterns.resolver.test(classContent)) {
|
|
330
|
+
componentType = "resolver";
|
|
331
|
+
angularType = "resolver";
|
|
332
|
+
}
|
|
333
|
+
else if (this.angularPatterns.validator.test(classContent)) {
|
|
334
|
+
componentType = "validator";
|
|
335
|
+
angularType = "validator";
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Default to service if no specific pattern matches
|
|
339
|
+
componentType = "service";
|
|
340
|
+
angularType = "service";
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// If still no type, check patterns one more time (for classes without decorators)
|
|
344
|
+
if (!componentType) {
|
|
345
|
+
const classContent = content.substring(classNode.range[0], classNode.range[1]);
|
|
346
|
+
if (this.angularPatterns.guard.test(classContent)) {
|
|
347
|
+
componentType = "guard";
|
|
348
|
+
angularType = "guard";
|
|
349
|
+
}
|
|
350
|
+
else if (this.angularPatterns.interceptor.test(classContent)) {
|
|
351
|
+
componentType = "interceptor";
|
|
352
|
+
angularType = "interceptor";
|
|
353
|
+
}
|
|
354
|
+
else if (this.angularPatterns.resolver.test(classContent)) {
|
|
355
|
+
componentType = "resolver";
|
|
356
|
+
angularType = "resolver";
|
|
357
|
+
}
|
|
358
|
+
else if (this.angularPatterns.validator.test(classContent)) {
|
|
359
|
+
componentType = "validator";
|
|
360
|
+
angularType = "validator";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Extract decorator metadata
|
|
364
|
+
const decoratorMetadata = this.extractDecoratorMetadata(decorator);
|
|
365
|
+
// Extract lifecycle hooks
|
|
366
|
+
const lifecycle = this.extractLifecycleHooks(classNode);
|
|
367
|
+
// Extract injected dependencies
|
|
368
|
+
const injectedServices = this.extractInjectedServices(classNode);
|
|
369
|
+
// Extract inputs and outputs
|
|
370
|
+
const inputs = this.extractInputs(classNode);
|
|
371
|
+
const outputs = this.extractOutputs(classNode);
|
|
372
|
+
return {
|
|
373
|
+
name: classNode.id.name,
|
|
374
|
+
type: "class",
|
|
375
|
+
componentType,
|
|
376
|
+
startLine: classNode.loc.start.line,
|
|
377
|
+
endLine: classNode.loc.end.line,
|
|
378
|
+
decorators: [
|
|
379
|
+
{
|
|
380
|
+
name: decoratorName,
|
|
381
|
+
properties: decoratorMetadata,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
lifecycle,
|
|
385
|
+
dependencies: injectedServices,
|
|
386
|
+
properties: [...inputs, ...outputs],
|
|
387
|
+
metadata: {
|
|
388
|
+
angularType,
|
|
389
|
+
selector: decoratorMetadata.selector,
|
|
390
|
+
isStandalone: decoratorMetadata.standalone === true,
|
|
391
|
+
template: decoratorMetadata.template,
|
|
392
|
+
templateUrl: decoratorMetadata.templateUrl,
|
|
393
|
+
styleUrls: decoratorMetadata.styleUrls,
|
|
394
|
+
inputs: inputs.map((i) => i.name),
|
|
395
|
+
outputs: outputs.map((o) => o.name),
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
extractDecoratorMetadata(decorator) {
|
|
400
|
+
const metadata = {};
|
|
401
|
+
try {
|
|
402
|
+
if (decorator.expression.arguments && decorator.expression.arguments[0]) {
|
|
403
|
+
const arg = decorator.expression.arguments[0];
|
|
404
|
+
if (arg.type === "ObjectExpression") {
|
|
405
|
+
for (const prop of arg.properties) {
|
|
406
|
+
if (prop.key && prop.value) {
|
|
407
|
+
const key = prop.key.name || prop.key.value;
|
|
408
|
+
if (prop.value.type === "Literal") {
|
|
409
|
+
metadata[key] = prop.value.value;
|
|
410
|
+
}
|
|
411
|
+
else if (prop.value.type === "ArrayExpression") {
|
|
412
|
+
metadata[key] = prop.value.elements
|
|
413
|
+
.map((el) => (el.type === "Literal" ? el.value : null))
|
|
414
|
+
.filter(Boolean);
|
|
415
|
+
}
|
|
416
|
+
else if (prop.value.type === "Identifier") {
|
|
417
|
+
metadata[key] = prop.value.name;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.warn("Failed to extract decorator metadata:", error);
|
|
426
|
+
}
|
|
427
|
+
return metadata;
|
|
428
|
+
}
|
|
429
|
+
extractLifecycleHooks(classNode) {
|
|
430
|
+
const hooks = [];
|
|
431
|
+
const lifecycleHooks = [
|
|
432
|
+
"ngOnChanges",
|
|
433
|
+
"ngOnInit",
|
|
434
|
+
"ngDoCheck",
|
|
435
|
+
"ngAfterContentInit",
|
|
436
|
+
"ngAfterContentChecked",
|
|
437
|
+
"ngAfterViewInit",
|
|
438
|
+
"ngAfterViewChecked",
|
|
439
|
+
"ngOnDestroy",
|
|
440
|
+
];
|
|
441
|
+
if (classNode.body && classNode.body.body) {
|
|
442
|
+
for (const member of classNode.body.body) {
|
|
443
|
+
if (member.type === "MethodDefinition" && member.key) {
|
|
444
|
+
const methodName = member.key.name;
|
|
445
|
+
if (lifecycleHooks.includes(methodName)) {
|
|
446
|
+
hooks.push(methodName);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return hooks;
|
|
452
|
+
}
|
|
453
|
+
extractInjectedServices(classNode) {
|
|
454
|
+
const services = [];
|
|
455
|
+
// Look for constructor parameters
|
|
456
|
+
if (classNode.body && classNode.body.body) {
|
|
457
|
+
for (const member of classNode.body.body) {
|
|
458
|
+
if (member.type === "MethodDefinition" &&
|
|
459
|
+
member.kind === "constructor") {
|
|
460
|
+
if (member.value.params) {
|
|
461
|
+
for (const param of member.value.params) {
|
|
462
|
+
if (param.typeAnnotation?.typeAnnotation?.typeName) {
|
|
463
|
+
services.push(param.typeAnnotation.typeAnnotation.typeName.name);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return services;
|
|
471
|
+
}
|
|
472
|
+
extractInputs(classNode) {
|
|
473
|
+
const inputs = [];
|
|
474
|
+
if (classNode.body && classNode.body.body) {
|
|
475
|
+
for (const member of classNode.body.body) {
|
|
476
|
+
if (member.type === "PropertyDefinition") {
|
|
477
|
+
// Check for decorator-based @Input()
|
|
478
|
+
if (member.decorators) {
|
|
479
|
+
const hasInput = member.decorators.some((d) => d.expression?.callee?.name === "Input" ||
|
|
480
|
+
d.expression?.name === "Input");
|
|
481
|
+
if (hasInput && member.key) {
|
|
482
|
+
inputs.push({
|
|
483
|
+
name: member.key.name,
|
|
484
|
+
type: member.typeAnnotation?.typeAnnotation?.type || "any",
|
|
485
|
+
style: "decorator",
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Check for signal-based input() (Angular v17.1+)
|
|
490
|
+
if (member.value && member.key) {
|
|
491
|
+
const valueStr = member.value.type === "CallExpression"
|
|
492
|
+
? member.value.callee?.name || member.value.callee?.object?.name
|
|
493
|
+
: null;
|
|
494
|
+
if (valueStr === "input") {
|
|
495
|
+
inputs.push({
|
|
496
|
+
name: member.key.name,
|
|
497
|
+
type: "InputSignal",
|
|
498
|
+
style: "signal",
|
|
499
|
+
required: member.value.callee?.property?.name === "required",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return inputs;
|
|
507
|
+
}
|
|
508
|
+
extractOutputs(classNode) {
|
|
509
|
+
const outputs = [];
|
|
510
|
+
if (classNode.body && classNode.body.body) {
|
|
511
|
+
for (const member of classNode.body.body) {
|
|
512
|
+
if (member.type === "PropertyDefinition") {
|
|
513
|
+
// Check for decorator-based @Output()
|
|
514
|
+
if (member.decorators) {
|
|
515
|
+
const hasOutput = member.decorators.some((d) => d.expression?.callee?.name === "Output" ||
|
|
516
|
+
d.expression?.name === "Output");
|
|
517
|
+
if (hasOutput && member.key) {
|
|
518
|
+
outputs.push({
|
|
519
|
+
name: member.key.name,
|
|
520
|
+
type: "EventEmitter",
|
|
521
|
+
style: "decorator",
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Check for signal-based output() (Angular v17.1+)
|
|
526
|
+
if (member.value && member.key) {
|
|
527
|
+
const valueStr = member.value.type === "CallExpression"
|
|
528
|
+
? member.value.callee?.name
|
|
529
|
+
: null;
|
|
530
|
+
if (valueStr === "output") {
|
|
531
|
+
outputs.push({
|
|
532
|
+
name: member.key.name,
|
|
533
|
+
type: "OutputEmitterRef",
|
|
534
|
+
style: "signal",
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return outputs;
|
|
542
|
+
}
|
|
543
|
+
async analyzeTemplateFile(filePath, content, relativePath) {
|
|
544
|
+
// Find corresponding component file
|
|
545
|
+
const componentPath = filePath.replace(/\.html$/, ".ts");
|
|
546
|
+
// Detect legacy vs modern control flow
|
|
547
|
+
const hasLegacyDirectives = /\*ng(?:If|For|Switch)/.test(content);
|
|
548
|
+
const hasModernControlFlow = /@(?:if|for|switch|defer)\s*[({]/.test(content);
|
|
549
|
+
return {
|
|
550
|
+
filePath,
|
|
551
|
+
language: "html",
|
|
552
|
+
framework: "angular",
|
|
553
|
+
components: [],
|
|
554
|
+
imports: [],
|
|
555
|
+
exports: [],
|
|
556
|
+
dependencies: [],
|
|
557
|
+
metadata: {
|
|
558
|
+
analyzer: this.name,
|
|
559
|
+
type: "template",
|
|
560
|
+
componentPath,
|
|
561
|
+
hasLegacyDirectives,
|
|
562
|
+
hasModernControlFlow,
|
|
563
|
+
hasBindings: /\[|\(|{{/.test(content),
|
|
564
|
+
hasDefer: /@defer\s*[({]/.test(content),
|
|
565
|
+
},
|
|
566
|
+
chunks: await createChunksFromCode(content, filePath, relativePath, "html", []),
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
async analyzeStyleFile(filePath, content, relativePath) {
|
|
570
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
571
|
+
const language = ext.substring(1); // Remove the dot
|
|
572
|
+
return {
|
|
573
|
+
filePath,
|
|
574
|
+
language,
|
|
575
|
+
framework: "angular",
|
|
576
|
+
components: [],
|
|
577
|
+
imports: [],
|
|
578
|
+
exports: [],
|
|
579
|
+
dependencies: [],
|
|
580
|
+
metadata: {
|
|
581
|
+
analyzer: this.name,
|
|
582
|
+
type: "style",
|
|
583
|
+
},
|
|
584
|
+
chunks: await createChunksFromCode(content, filePath, relativePath, language, []),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
detectStateManagement(content) {
|
|
588
|
+
for (const [pattern, regex] of Object.entries(this.stateManagementPatterns)) {
|
|
589
|
+
if (regex.test(content)) {
|
|
590
|
+
return pattern;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
determineLayer(filePath, components) {
|
|
596
|
+
const lowerPath = filePath.toLowerCase();
|
|
597
|
+
// Check path-based patterns
|
|
598
|
+
if (lowerPath.includes("/component") ||
|
|
599
|
+
lowerPath.includes("/view") ||
|
|
600
|
+
lowerPath.includes("/page")) {
|
|
601
|
+
return "presentation";
|
|
602
|
+
}
|
|
603
|
+
if (lowerPath.includes("/service")) {
|
|
604
|
+
return "business";
|
|
605
|
+
}
|
|
606
|
+
if (lowerPath.includes("/data") ||
|
|
607
|
+
lowerPath.includes("/repository") ||
|
|
608
|
+
lowerPath.includes("/api")) {
|
|
609
|
+
return "data";
|
|
610
|
+
}
|
|
611
|
+
if (lowerPath.includes("/store") ||
|
|
612
|
+
lowerPath.includes("/state") ||
|
|
613
|
+
lowerPath.includes("/ngrx")) {
|
|
614
|
+
return "state";
|
|
615
|
+
}
|
|
616
|
+
if (lowerPath.includes("/core")) {
|
|
617
|
+
return "core";
|
|
618
|
+
}
|
|
619
|
+
if (lowerPath.includes("/shared")) {
|
|
620
|
+
return "shared";
|
|
621
|
+
}
|
|
622
|
+
if (lowerPath.includes("/feature")) {
|
|
623
|
+
return "feature";
|
|
624
|
+
}
|
|
625
|
+
// Check component types
|
|
626
|
+
for (const component of components) {
|
|
627
|
+
if (component.componentType === "component" ||
|
|
628
|
+
component.componentType === "directive" ||
|
|
629
|
+
component.componentType === "pipe") {
|
|
630
|
+
return "presentation";
|
|
631
|
+
}
|
|
632
|
+
if (component.componentType === "service") {
|
|
633
|
+
return lowerPath.includes("http") || lowerPath.includes("api")
|
|
634
|
+
? "data"
|
|
635
|
+
: "business";
|
|
636
|
+
}
|
|
637
|
+
if (component.componentType === "guard" ||
|
|
638
|
+
component.componentType === "interceptor") {
|
|
639
|
+
return "core";
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return "unknown";
|
|
643
|
+
}
|
|
644
|
+
categorizeDependency(name) {
|
|
645
|
+
if (name.startsWith("@angular/")) {
|
|
646
|
+
return "framework";
|
|
647
|
+
}
|
|
648
|
+
if (name.includes("ngrx") ||
|
|
649
|
+
name.includes("akita") ||
|
|
650
|
+
name.includes("elf")) {
|
|
651
|
+
return "state";
|
|
652
|
+
}
|
|
653
|
+
if (name.includes("material") ||
|
|
654
|
+
name.includes("primeng") ||
|
|
655
|
+
name.includes("ng-bootstrap")) {
|
|
656
|
+
return "ui";
|
|
657
|
+
}
|
|
658
|
+
if (name.includes("router")) {
|
|
659
|
+
return "routing";
|
|
660
|
+
}
|
|
661
|
+
if (name.includes("http") || name.includes("common/http")) {
|
|
662
|
+
return "http";
|
|
663
|
+
}
|
|
664
|
+
if (name.includes("test") ||
|
|
665
|
+
name.includes("jest") ||
|
|
666
|
+
name.includes("jasmine") ||
|
|
667
|
+
name.includes("karma")) {
|
|
668
|
+
return "testing";
|
|
669
|
+
}
|
|
670
|
+
return "other";
|
|
671
|
+
}
|
|
672
|
+
async detectCodebaseMetadata(rootPath) {
|
|
673
|
+
const metadata = {
|
|
674
|
+
name: path.basename(rootPath),
|
|
675
|
+
rootPath,
|
|
676
|
+
languages: [],
|
|
677
|
+
dependencies: [],
|
|
678
|
+
architecture: {
|
|
679
|
+
type: "feature-based",
|
|
680
|
+
layers: {
|
|
681
|
+
presentation: 0,
|
|
682
|
+
business: 0,
|
|
683
|
+
data: 0,
|
|
684
|
+
state: 0,
|
|
685
|
+
core: 0,
|
|
686
|
+
shared: 0,
|
|
687
|
+
feature: 0,
|
|
688
|
+
infrastructure: 0,
|
|
689
|
+
unknown: 0,
|
|
690
|
+
},
|
|
691
|
+
patterns: [],
|
|
692
|
+
},
|
|
693
|
+
styleGuides: [],
|
|
694
|
+
documentation: [],
|
|
695
|
+
projectStructure: {
|
|
696
|
+
type: "single-app",
|
|
697
|
+
},
|
|
698
|
+
statistics: {
|
|
699
|
+
totalFiles: 0,
|
|
700
|
+
totalLines: 0,
|
|
701
|
+
totalComponents: 0,
|
|
702
|
+
componentsByType: {},
|
|
703
|
+
componentsByLayer: {
|
|
704
|
+
presentation: 0,
|
|
705
|
+
business: 0,
|
|
706
|
+
data: 0,
|
|
707
|
+
state: 0,
|
|
708
|
+
core: 0,
|
|
709
|
+
shared: 0,
|
|
710
|
+
feature: 0,
|
|
711
|
+
infrastructure: 0,
|
|
712
|
+
unknown: 0,
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
customMetadata: {},
|
|
716
|
+
};
|
|
717
|
+
try {
|
|
718
|
+
// Read package.json
|
|
719
|
+
const packageJsonPath = path.join(rootPath, "package.json");
|
|
720
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
721
|
+
metadata.name = packageJson.name || metadata.name;
|
|
722
|
+
// Extract Angular version and dependencies
|
|
723
|
+
const allDeps = {
|
|
724
|
+
...packageJson.dependencies,
|
|
725
|
+
...packageJson.devDependencies,
|
|
726
|
+
};
|
|
727
|
+
const angularVersion = allDeps["@angular/core"]?.replace(/[\^~]/, "") || "unknown";
|
|
728
|
+
// Detect state management
|
|
729
|
+
const stateManagement = [];
|
|
730
|
+
if (allDeps["@ngrx/store"])
|
|
731
|
+
stateManagement.push("ngrx");
|
|
732
|
+
if (allDeps["@datorama/akita"])
|
|
733
|
+
stateManagement.push("akita");
|
|
734
|
+
if (allDeps["@ngneat/elf"])
|
|
735
|
+
stateManagement.push("elf");
|
|
736
|
+
// Detect UI libraries
|
|
737
|
+
const uiLibraries = [];
|
|
738
|
+
if (allDeps["@angular/material"])
|
|
739
|
+
uiLibraries.push("Angular Material");
|
|
740
|
+
if (allDeps["primeng"])
|
|
741
|
+
uiLibraries.push("PrimeNG");
|
|
742
|
+
if (allDeps["@ng-bootstrap/ng-bootstrap"])
|
|
743
|
+
uiLibraries.push("ng-bootstrap");
|
|
744
|
+
// Detect testing frameworks
|
|
745
|
+
const testingFrameworks = [];
|
|
746
|
+
if (allDeps["jasmine-core"])
|
|
747
|
+
testingFrameworks.push("Jasmine");
|
|
748
|
+
if (allDeps["karma"])
|
|
749
|
+
testingFrameworks.push("Karma");
|
|
750
|
+
if (allDeps["jest"])
|
|
751
|
+
testingFrameworks.push("Jest");
|
|
752
|
+
metadata.framework = {
|
|
753
|
+
name: "Angular",
|
|
754
|
+
version: angularVersion,
|
|
755
|
+
type: "angular",
|
|
756
|
+
variant: "unknown", // Will be determined during analysis
|
|
757
|
+
stateManagement,
|
|
758
|
+
uiLibraries,
|
|
759
|
+
testingFrameworks,
|
|
760
|
+
};
|
|
761
|
+
// Convert dependencies
|
|
762
|
+
metadata.dependencies = Object.entries(allDeps).map(([name, version]) => ({
|
|
763
|
+
name,
|
|
764
|
+
version: version,
|
|
765
|
+
category: this.categorizeDependency(name),
|
|
766
|
+
}));
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
console.warn("Failed to read Angular project metadata:", error);
|
|
770
|
+
}
|
|
771
|
+
// Calculate statistics from existing index if available
|
|
772
|
+
try {
|
|
773
|
+
const indexPath = path.join(rootPath, ".codebase-index.json");
|
|
774
|
+
const indexContent = await fs.readFile(indexPath, "utf-8");
|
|
775
|
+
const chunks = JSON.parse(indexContent);
|
|
776
|
+
console.error(`Loading statistics from ${indexPath}: ${chunks.length} chunks`);
|
|
777
|
+
if (Array.isArray(chunks) && chunks.length > 0) {
|
|
778
|
+
metadata.statistics.totalFiles = new Set(chunks.map((c) => c.filePath)).size;
|
|
779
|
+
metadata.statistics.totalLines = chunks.reduce((sum, c) => sum + (c.endLine - c.startLine + 1), 0);
|
|
780
|
+
// Count components by type
|
|
781
|
+
const componentCounts = {};
|
|
782
|
+
const layerCounts = {
|
|
783
|
+
presentation: 0,
|
|
784
|
+
business: 0,
|
|
785
|
+
data: 0,
|
|
786
|
+
state: 0,
|
|
787
|
+
core: 0,
|
|
788
|
+
shared: 0,
|
|
789
|
+
feature: 0,
|
|
790
|
+
infrastructure: 0,
|
|
791
|
+
unknown: 0,
|
|
792
|
+
};
|
|
793
|
+
for (const chunk of chunks) {
|
|
794
|
+
if (chunk.componentType) {
|
|
795
|
+
componentCounts[chunk.componentType] =
|
|
796
|
+
(componentCounts[chunk.componentType] || 0) + 1;
|
|
797
|
+
metadata.statistics.totalComponents++;
|
|
798
|
+
}
|
|
799
|
+
if (chunk.layer) {
|
|
800
|
+
layerCounts[chunk.layer] =
|
|
801
|
+
(layerCounts[chunk.layer] || 0) + 1;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
metadata.statistics.componentsByType = componentCounts;
|
|
805
|
+
metadata.statistics.componentsByLayer = layerCounts;
|
|
806
|
+
metadata.architecture.layers = layerCounts;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
// Index doesn't exist yet, keep statistics at 0
|
|
811
|
+
console.warn("Failed to calculate statistics from index:", error);
|
|
812
|
+
}
|
|
813
|
+
return metadata;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Generate Angular-specific summary for a code chunk
|
|
817
|
+
*/
|
|
818
|
+
summarize(chunk) {
|
|
819
|
+
const { componentType, metadata, content } = chunk;
|
|
820
|
+
const fileName = path.basename(chunk.filePath);
|
|
821
|
+
// Extract class/component name
|
|
822
|
+
const classMatch = content.match(/(?:export\s+)?class\s+(\w+)/);
|
|
823
|
+
const className = classMatch ? classMatch[1] : fileName;
|
|
824
|
+
switch (componentType) {
|
|
825
|
+
case "component":
|
|
826
|
+
const selector = metadata.decorator?.selector || "unknown";
|
|
827
|
+
const inputs = metadata.decorator?.inputs?.length || 0;
|
|
828
|
+
const outputs = metadata.decorator?.outputs?.length || 0;
|
|
829
|
+
const lifecycle = this.extractLifecycleMethods(content);
|
|
830
|
+
return `Angular component '${className}' (selector: ${selector})${lifecycle ? ` with ${lifecycle}` : ""}${inputs ? `, ${inputs} inputs` : ""}${outputs ? `, ${outputs} outputs` : ""}.`;
|
|
831
|
+
case "service":
|
|
832
|
+
const providedIn = metadata.decorator?.providedIn || "unknown";
|
|
833
|
+
const methods = this.extractPublicMethods(content);
|
|
834
|
+
return `Angular service '${className}' (providedIn: ${providedIn})${methods ? ` providing ${methods}` : ""}.`;
|
|
835
|
+
case "guard":
|
|
836
|
+
const guardType = this.detectGuardType(content);
|
|
837
|
+
return `Angular ${guardType} guard '${className}' protecting routes.`;
|
|
838
|
+
case "directive":
|
|
839
|
+
const directiveSelector = metadata.decorator?.selector || "unknown";
|
|
840
|
+
return `Angular directive '${className}' (selector: ${directiveSelector}).`;
|
|
841
|
+
case "pipe":
|
|
842
|
+
const pipeName = metadata.decorator?.name || "unknown";
|
|
843
|
+
return `Angular pipe '${className}' (name: ${pipeName}) for data transformation.`;
|
|
844
|
+
case "module":
|
|
845
|
+
const imports = metadata.decorator?.imports?.length || 0;
|
|
846
|
+
const declarations = metadata.decorator?.declarations?.length || 0;
|
|
847
|
+
return `Angular module '${className}' with ${declarations} declarations and ${imports} imports.`;
|
|
848
|
+
case "interceptor":
|
|
849
|
+
return `Angular HTTP interceptor '${className}' modifying HTTP requests/responses.`;
|
|
850
|
+
case "resolver":
|
|
851
|
+
return `Angular resolver '${className}' pre-fetching route data.`;
|
|
852
|
+
case "validator":
|
|
853
|
+
return `Angular validator '${className}' for form validation.`;
|
|
854
|
+
default:
|
|
855
|
+
// Try to provide a meaningful fallback
|
|
856
|
+
if (className && className !== fileName) {
|
|
857
|
+
// Check for common patterns
|
|
858
|
+
if (content.includes("signal(") ||
|
|
859
|
+
content.includes("computed(") ||
|
|
860
|
+
content.includes("effect(")) {
|
|
861
|
+
return `Angular code '${className}' using signals.`;
|
|
862
|
+
}
|
|
863
|
+
if (content.includes("inject(")) {
|
|
864
|
+
return `Angular code '${className}' using dependency injection.`;
|
|
865
|
+
}
|
|
866
|
+
if (content.includes("Observable") || content.includes("Subject")) {
|
|
867
|
+
return `Angular code '${className}' with reactive streams.`;
|
|
868
|
+
}
|
|
869
|
+
return `Angular code '${className}' in ${fileName}.`;
|
|
870
|
+
}
|
|
871
|
+
// Extract first meaningful export or declaration
|
|
872
|
+
const exportMatch = content.match(/export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/);
|
|
873
|
+
if (exportMatch) {
|
|
874
|
+
return `Exports '${exportMatch[1]}' from ${fileName}.`;
|
|
875
|
+
}
|
|
876
|
+
return `Angular code in ${fileName}.`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
extractLifecycleMethods(content) {
|
|
880
|
+
const lifecycles = [
|
|
881
|
+
"ngOnInit",
|
|
882
|
+
"ngOnChanges",
|
|
883
|
+
"ngOnDestroy",
|
|
884
|
+
"ngAfterViewInit",
|
|
885
|
+
"ngAfterContentInit",
|
|
886
|
+
];
|
|
887
|
+
const found = lifecycles.filter((method) => content.includes(method));
|
|
888
|
+
return found.length > 0 ? found.join(", ") : "";
|
|
889
|
+
}
|
|
890
|
+
extractPublicMethods(content) {
|
|
891
|
+
const methodMatches = content.match(/public\s+(\w+)\s*\(/g);
|
|
892
|
+
if (!methodMatches || methodMatches.length === 0)
|
|
893
|
+
return "";
|
|
894
|
+
const methods = methodMatches
|
|
895
|
+
.slice(0, 3)
|
|
896
|
+
.map((m) => m.match(/public\s+(\w+)/)?.[1])
|
|
897
|
+
.filter(Boolean);
|
|
898
|
+
return methods.length > 0 ? `methods: ${methods.join(", ")}` : "";
|
|
899
|
+
}
|
|
900
|
+
detectGuardType(content) {
|
|
901
|
+
if (content.includes("CanActivate"))
|
|
902
|
+
return "CanActivate";
|
|
903
|
+
if (content.includes("CanDeactivate"))
|
|
904
|
+
return "CanDeactivate";
|
|
905
|
+
if (content.includes("CanLoad"))
|
|
906
|
+
return "CanLoad";
|
|
907
|
+
if (content.includes("CanMatch"))
|
|
908
|
+
return "CanMatch";
|
|
909
|
+
return "route";
|
|
910
|
+
}
|
|
911
|
+
extractFirstComment(content) {
|
|
912
|
+
const commentMatch = content.match(/\/\*\*\s*\n?\s*\*\s*(.+?)(?:\n|\*\/)/);
|
|
913
|
+
return commentMatch ? commentMatch[1].trim() : "";
|
|
914
|
+
}
|
|
915
|
+
extractFirstLine(content) {
|
|
916
|
+
const firstLine = content
|
|
917
|
+
.split("\n")
|
|
918
|
+
.find((line) => line.trim() && !line.trim().startsWith("import"));
|
|
919
|
+
return firstLine ? firstLine.trim().slice(0, 60) + "..." : "";
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
//# sourceMappingURL=index.js.map
|