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