@sun-asterisk/impact-analyzer 1.0.4 → 1.0.6
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/.github/copilot-instructions.md +116 -0
- package/.github/prompts/README.md +91 -0
- package/.github/prompts/task-001-refactor.prompt.md +241 -0
- package/.specify/bugs/bug-001-database-detector.md +222 -0
- package/.specify/bugs/bug-002-database-detector.md +478 -0
- package/.specify/bugs/bug-003-multiline-detection.md +527 -0
- package/.specify/plans/architecture.md +186 -0
- package/.specify/specs/features/api-impact-detection.md +317 -0
- package/.specify/specs/features/component-impact-detection.md +263 -0
- package/.specify/specs/features/database-impact-detection.md +247 -0
- package/.specify/tasks/task-001-refactor-api-detector.md +284 -0
- package/.specify/tasks/task-002-database-detector.md +593 -0
- package/.specify/tasks/task-003-component-detector.md +0 -0
- package/.specify/tasks/task-004-report.md +484 -0
- package/README.md +13 -19
- package/core/detectors/database-detector.js +912 -0
- package/{modules → core}/detectors/endpoint-detector.js +11 -8
- package/{modules → core}/report-generator.js +102 -20
- package/core/utils/logger.js +12 -0
- package/index.js +6 -5
- package/package.json +1 -1
- package/modules/detectors/database-detector.js +0 -182
- /package/{modules → core}/change-detector.js +0 -0
- /package/{modules → core}/impact-analyzer.js +0 -0
- /package/{modules → core}/utils/ast-parser.js +0 -0
- /package/{modules → core}/utils/dependency-graph.js +0 -0
- /package/{modules → core}/utils/file-utils.js +0 -0
- /package/{modules → core}/utils/git-utils.js +0 -0
- /package/{modules → core}/utils/method-call-graph.js +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Feature Spec: API Impact Detection
|
|
2
|
+
|
|
3
|
+
## What It Does
|
|
4
|
+
|
|
5
|
+
Finds API endpoints affected by code changes using call graph traversal.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
**Input:** Changed methods from git diff
|
|
10
|
+
**Output:** List of affected API endpoints with HTTP method and path
|
|
11
|
+
|
|
12
|
+
### Algorithm
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
function findAffectedEndpoints(changedMethod, callGraph) {
|
|
16
|
+
// 1. Start from changed method
|
|
17
|
+
// 2. Traverse upward through callers
|
|
18
|
+
// 3. Stop when reaching method with HTTP decorator
|
|
19
|
+
// 4. Extract endpoint info (method, path)
|
|
20
|
+
|
|
21
|
+
const endpoints = [];
|
|
22
|
+
const visited = new Set();
|
|
23
|
+
|
|
24
|
+
function traverse(method) {
|
|
25
|
+
if (visited.has(method)) return;
|
|
26
|
+
visited.add(method);
|
|
27
|
+
|
|
28
|
+
// Check if this is an endpoint
|
|
29
|
+
if (hasHttpDecorator(method)) {
|
|
30
|
+
endpoints.push({
|
|
31
|
+
httpMethod: extractHttpMethod(method),
|
|
32
|
+
path: extractPath(method),
|
|
33
|
+
controller: method.name
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Continue traversing up
|
|
39
|
+
const callers = callGraph.getCallers(method);
|
|
40
|
+
callers.forEach(traverse);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
traverse(changedMethod);
|
|
44
|
+
return endpoints;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Flow Example
|
|
49
|
+
|
|
50
|
+
**Synchronous (Direct Call):**
|
|
51
|
+
```
|
|
52
|
+
Changed: UserService.updateEmail()
|
|
53
|
+
↓
|
|
54
|
+
Caller: UserController.updateProfile()
|
|
55
|
+
↓
|
|
56
|
+
Check: Has @Put(':id') decorator
|
|
57
|
+
↓
|
|
58
|
+
Output: PUT /users/:id
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Asynchronous (Command/Queue):**
|
|
62
|
+
```
|
|
63
|
+
Changed: OrderService.processOrder()
|
|
64
|
+
↓
|
|
65
|
+
Caller: ProcessOrderHandler.handle()
|
|
66
|
+
↓
|
|
67
|
+
Check: Has @Process('processOrder') decorator
|
|
68
|
+
↓
|
|
69
|
+
Search: Find where 'processOrder' command is published
|
|
70
|
+
↓
|
|
71
|
+
Found: OrderController.create() with queue.add('processOrder')
|
|
72
|
+
↓
|
|
73
|
+
Check: Has @Post('orders') decorator
|
|
74
|
+
↓
|
|
75
|
+
Output: POST /orders (via command: processOrder)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Supported Frameworks
|
|
79
|
+
|
|
80
|
+
### NestJS (Primary)
|
|
81
|
+
|
|
82
|
+
**HTTP Decorators:**
|
|
83
|
+
- `@Controller('path')` - Base path
|
|
84
|
+
- `@Get('subpath')` - GET method
|
|
85
|
+
- `@Post('subpath')` - POST method
|
|
86
|
+
- `@Put('subpath')` - PUT method
|
|
87
|
+
- `@Delete('subpath')` - DELETE method
|
|
88
|
+
- `@Patch('subpath')` - PATCH method
|
|
89
|
+
|
|
90
|
+
**Queue/Command Decorators:**
|
|
91
|
+
- `@Process('commandName')` - Queue processor
|
|
92
|
+
- `@OnQueueCompleted()` - Queue event handler
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// Detection logic for HTTP endpoints
|
|
96
|
+
function hasHttpDecorator(method) {
|
|
97
|
+
const decorators = ['Get', 'Post', 'Put', 'Delete', 'Patch'];
|
|
98
|
+
return method.decorators.some(d => decorators.includes(d.name));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Detection logic for queue handlers
|
|
102
|
+
function isQueueHandler(method) {
|
|
103
|
+
return method.decorators.some(d => d.name === 'Process');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractCommandName(method) {
|
|
107
|
+
const processDecorator = method.decorators.find(d => d.name === 'Process');
|
|
108
|
+
return processDecorator?.arguments[0]; // Returns 'processOrder'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extractEndpointInfo(method) {
|
|
112
|
+
const httpDecorator = method.decorators.find(d =>
|
|
113
|
+
['Get', 'Post', 'Put', 'Delete', 'Patch'].includes(d.name)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const controllerDecorator = method.class.decorators.find(d =>
|
|
117
|
+
d.name === 'Controller'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
method: httpDecorator.name.toUpperCase(),
|
|
122
|
+
path: joinPath(
|
|
123
|
+
controllerDecorator.arguments[0],
|
|
124
|
+
httpDecorator.arguments[0]
|
|
125
|
+
)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Express
|
|
131
|
+
|
|
132
|
+
Detect route definitions:
|
|
133
|
+
- `router.get(path, handler)`
|
|
134
|
+
- `router.post(path, handler)`
|
|
135
|
+
- `app.get(path, handler)`
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// Detection logic
|
|
139
|
+
function isExpressRoute(callExpression) {
|
|
140
|
+
const methods = ['get', 'post', 'put', 'delete', 'patch'];
|
|
141
|
+
return callExpression.expression.object.name === 'router' &&
|
|
142
|
+
methods.includes(callExpression.expression.property.name);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Async Patterns (Command/Queue)
|
|
147
|
+
|
|
148
|
+
### Queue Publishers
|
|
149
|
+
|
|
150
|
+
Detect queue/command publishing patterns:
|
|
151
|
+
- BullMQ: `queue.add('commandName', data)`
|
|
152
|
+
- NestJS Queue: `await this.queue.add('commandName', data)`
|
|
153
|
+
- AWS SQS: `await sqs.send({ command: 'commandName' })`
|
|
154
|
+
- AWS SNS: `await sns.send({ message: { command: 'commandName' } })`
|
|
155
|
+
- Command Bus: `await commandBus.execute('commandName', data)`
|
|
156
|
+
|
|
157
|
+
### Detection Algorithm for Async Flow
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
function findAsyncEndpoints(changedMethod, callGraph) {
|
|
161
|
+
const endpoints = [];
|
|
162
|
+
|
|
163
|
+
// 1. Traverse up to find queue handler
|
|
164
|
+
const handler = findQueueHandler(changedMethod, callGraph);
|
|
165
|
+
if (!handler) return endpoints;
|
|
166
|
+
|
|
167
|
+
// 2. Extract command name from handler decorator
|
|
168
|
+
const commandName = extractCommandName(handler);
|
|
169
|
+
if (!commandName) return endpoints;
|
|
170
|
+
|
|
171
|
+
// 3. Search for publishers of this command
|
|
172
|
+
const publishers = findCommandPublishers(commandName);
|
|
173
|
+
|
|
174
|
+
// 4. For each publisher, find the endpoint
|
|
175
|
+
for (const publisher of publishers) {
|
|
176
|
+
const endpoint = findEndpointForMethod(publisher, callGraph);
|
|
177
|
+
if (endpoint) {
|
|
178
|
+
endpoints.push({
|
|
179
|
+
...endpoint,
|
|
180
|
+
flow: 'async',
|
|
181
|
+
command: commandName,
|
|
182
|
+
handler: handler.name
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return endpoints;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function findQueueHandler(method, callGraph) {
|
|
191
|
+
const callers = callGraph.getCallers(method);
|
|
192
|
+
for (const caller of callers) {
|
|
193
|
+
if (isQueueHandler(caller)) {
|
|
194
|
+
return caller;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function findCommandPublishers(commandName) {
|
|
201
|
+
// Search all files for queue.add('commandName')
|
|
202
|
+
const publishers = [];
|
|
203
|
+
const pattern = new RegExp(`\\.add\\(['"\`]${commandName}['"\`]`);
|
|
204
|
+
|
|
205
|
+
// Use AST to find call expressions matching pattern
|
|
206
|
+
// Returns array of methods that publish this command
|
|
207
|
+
|
|
208
|
+
return publishers;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Example: Async Flow
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Controller publishes command
|
|
216
|
+
@Controller('orders')
|
|
217
|
+
class OrderController {
|
|
218
|
+
@Post()
|
|
219
|
+
async create(@Body() dto: CreateOrderDto) {
|
|
220
|
+
const command = 'processOrder';
|
|
221
|
+
await this.sns.publishMessage({
|
|
222
|
+
message: { command: command, payload: dto }
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return { success: true };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Case 1. Handler processes command
|
|
230
|
+
@Processor('orderQueue')
|
|
231
|
+
class OrderProcessor {
|
|
232
|
+
@Process('processOrder')
|
|
233
|
+
async handle(job: Job) {
|
|
234
|
+
await this.orderService.processOrder(job.data); // Changed method
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Case 2. Handler command by nest
|
|
239
|
+
@command({name: 'processOrder'})
|
|
240
|
+
export class OrderCommand extends CommonComand {
|
|
241
|
+
async run() {
|
|
242
|
+
await this.orderService.processOrder(job.data); // Changed method
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Service method (changed)
|
|
246
|
+
class OrderService {
|
|
247
|
+
async processOrder(data) {
|
|
248
|
+
// Implementation changed here
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Detection result:
|
|
253
|
+
// POST /orders (via command: processOrder)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Output Structure
|
|
257
|
+
|
|
258
|
+
**Synchronous Endpoint:**
|
|
259
|
+
```javascript
|
|
260
|
+
{
|
|
261
|
+
type: 'endpoint',
|
|
262
|
+
flow: 'sync',
|
|
263
|
+
method: 'PUT', // HTTP method
|
|
264
|
+
path: '/users/:id', // Route path
|
|
265
|
+
controller: 'UserController.updateProfile',
|
|
266
|
+
changedMethod: 'UserService.updateEmail',
|
|
267
|
+
file: 'src/services/user.service.js'
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Asynchronous Endpoint:**
|
|
272
|
+
```javascript
|
|
273
|
+
{
|
|
274
|
+
type: 'endpoint',
|
|
275
|
+
flow: 'async',
|
|
276
|
+
method: 'POST',
|
|
277
|
+
path: '/orders',
|
|
278
|
+
controller: 'OrderController.create',
|
|
279
|
+
command: 'processOrder', // Command name
|
|
280
|
+
handler: 'OrderProcessor.handle', // Handler method
|
|
281
|
+
changedMethod: 'OrderService.processOrder',
|
|
282
|
+
file: 'src/services/order.service.js'
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Implementation Notes
|
|
287
|
+
|
|
288
|
+
**Call Graph Building:**
|
|
289
|
+
- Use `ts-morph` to parse TypeScript/JavaScript files
|
|
290
|
+
- Build reverse map: method → callers
|
|
291
|
+
- Store decorator information on methods
|
|
292
|
+
- Store command names from queue operations
|
|
293
|
+
|
|
294
|
+
**Traversal Strategy:**
|
|
295
|
+
- BFS (breadth-first search) to find closest endpoints first
|
|
296
|
+
- Stop at HTTP decorators (sync) or queue handlers (async)
|
|
297
|
+
- Track visited nodes to avoid cycles
|
|
298
|
+
- Max depth: 10 levels
|
|
299
|
+
|
|
300
|
+
**Async Detection Strategy:**
|
|
301
|
+
1. Traverse up from changed method to find queue handler
|
|
302
|
+
2. Extract command name from `@Process('commandName')`
|
|
303
|
+
3. Search codebase for `queue.add('commandName')` patterns
|
|
304
|
+
4. Find endpoint that publishes the command
|
|
305
|
+
5. Combine handler info with endpoint info
|
|
306
|
+
|
|
307
|
+
**Path Construction:**
|
|
308
|
+
- Combine `@Controller` path with HTTP decorator path
|
|
309
|
+
- Handle dynamic segments (`:id`, `:slug`)
|
|
310
|
+
- Normalize paths (remove trailing slashes)
|
|
311
|
+
|
|
312
|
+
**Edge Cases:**
|
|
313
|
+
- Skip if circular dependency detected
|
|
314
|
+
- Skip if max depth reached
|
|
315
|
+
- Report multiple endpoints if method has multiple callers
|
|
316
|
+
- For async: Skip if command name is dynamic/variable
|
|
317
|
+
- For async: Mark as external if command publisher not found
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Feature Spec: Component Impact Detection
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
Identify UI components affected by code changes. This feature traces component dependencies to find parent components that may be impacted by changes to child components.
|
|
6
|
+
|
|
7
|
+
## 2. User Story
|
|
8
|
+
|
|
9
|
+
> As a developer reviewing a pull request,
|
|
10
|
+
> I want to see which UI components are affected by the code changes,
|
|
11
|
+
> So that I can plan visual testing and assess user-facing impact.
|
|
12
|
+
|
|
13
|
+
## 3. Functional Requirements
|
|
14
|
+
|
|
15
|
+
### FR-001: Detect Component Definitions
|
|
16
|
+
- **Description**: Identify React/Vue/Angular component definitions
|
|
17
|
+
- **Patterns to Support**:
|
|
18
|
+
- React functional components
|
|
19
|
+
- React class components
|
|
20
|
+
- React. FC type annotation
|
|
21
|
+
- Vue Single File Components (SFC)
|
|
22
|
+
- Vue Composition API (script setup)
|
|
23
|
+
- Angular @Component decorator
|
|
24
|
+
- **Acceptance Criteria**:
|
|
25
|
+
- Detects function components (named and arrow)
|
|
26
|
+
- Detects class components extending React.Component
|
|
27
|
+
- Detects Vue SFC script blocks
|
|
28
|
+
- Detects Angular component classes
|
|
29
|
+
|
|
30
|
+
### FR-002: Build Component Dependency Tree
|
|
31
|
+
- **Description**: Map component import/usage relationships
|
|
32
|
+
- **Input**: Changed component names
|
|
33
|
+
- **Output**: Tree of parent components
|
|
34
|
+
- **Acceptance Criteria**:
|
|
35
|
+
- Traces JSX/TSX component usage
|
|
36
|
+
- Resolves import statements
|
|
37
|
+
- Handles re-exports
|
|
38
|
+
- Handles dynamic imports
|
|
39
|
+
|
|
40
|
+
### FR-003: Detect Props/Interface Changes
|
|
41
|
+
- **Description**: Identify changes to component props
|
|
42
|
+
- **Patterns to Support**:
|
|
43
|
+
- TypeScript interface: `interface Props { }`
|
|
44
|
+
- TypeScript type: `type Props = { }`
|
|
45
|
+
- PropTypes definition
|
|
46
|
+
- Vue defineProps
|
|
47
|
+
- Angular @Input() decorator
|
|
48
|
+
- **Acceptance Criteria**:
|
|
49
|
+
- Detects added props
|
|
50
|
+
- Detects removed props
|
|
51
|
+
- Detects prop type changes
|
|
52
|
+
- Flags breaking prop changes
|
|
53
|
+
|
|
54
|
+
### FR-004: Find Parent Components
|
|
55
|
+
- **Description**: Locate all components that use the changed component
|
|
56
|
+
- **Input**: Changed component name and file
|
|
57
|
+
- **Output**: List of parent components with usage count
|
|
58
|
+
- **Acceptance Criteria**:
|
|
59
|
+
- Searches all component files in project
|
|
60
|
+
- Finds JSX usage: `<ComponentName />`
|
|
61
|
+
- Handles aliased imports
|
|
62
|
+
- Counts usage instances
|
|
63
|
+
|
|
64
|
+
### FR-005: Classify Component Type
|
|
65
|
+
- **Description**: Categorize the component framework and style
|
|
66
|
+
- **Types**:
|
|
67
|
+
- `react-functional`: React function components
|
|
68
|
+
- `react-class`: React class components
|
|
69
|
+
- `vue-sfc`: Vue Single File Components
|
|
70
|
+
- `vue-composition`: Vue Composition API
|
|
71
|
+
- `angular`: Angular components
|
|
72
|
+
- **Acceptance Criteria**:
|
|
73
|
+
- Correctly identifies React components
|
|
74
|
+
- Correctly identifies Vue components
|
|
75
|
+
- Correctly identifies Angular components
|
|
76
|
+
|
|
77
|
+
### FR-006: Generate Component Report
|
|
78
|
+
- **Description**: Compile component impacts into structured report
|
|
79
|
+
- **Output**: `ComponentImpact[]` array
|
|
80
|
+
- **Acceptance Criteria**:
|
|
81
|
+
- Includes component name and type
|
|
82
|
+
- Includes parent component list
|
|
83
|
+
- Indicates props changes
|
|
84
|
+
- Includes severity based on usage
|
|
85
|
+
|
|
86
|
+
## 4. Non-Functional Requirements
|
|
87
|
+
|
|
88
|
+
### NFR-001: Performance
|
|
89
|
+
- Analysis should add <10 seconds to total analysis time
|
|
90
|
+
- Should handle projects with 500+ components
|
|
91
|
+
|
|
92
|
+
### NFR-002: Accuracy
|
|
93
|
+
- Should detect >90% of component changes
|
|
94
|
+
- Should find >85% of parent component relationships
|
|
95
|
+
|
|
96
|
+
## 5. Output Schema
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
/**
|
|
100
|
+
* @typedef {Object} ComponentImpact
|
|
101
|
+
* @property {string} componentName - Component name
|
|
102
|
+
* @property {string} filePath - Path to component file
|
|
103
|
+
* @property {'react-functional'|'react-class'|'vue-sfc'|'vue-composition'|'angular'} componentType
|
|
104
|
+
* @property {ParentComponent[]} parentComponents
|
|
105
|
+
* @property {boolean} propsChanged - Whether props interface was modified
|
|
106
|
+
* @property {FileChange} changeSource
|
|
107
|
+
* @property {'low'|'medium'|'high'|'critical'} severity
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @typedef {Object} ParentComponent
|
|
112
|
+
* @property {string} name - Parent component name
|
|
113
|
+
* @property {string} filePath - Path to parent component
|
|
114
|
+
* @property {number} usageCount - How many times child is used
|
|
115
|
+
*/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 6. Severity Classification
|
|
119
|
+
|
|
120
|
+
| Severity | Criteria |
|
|
121
|
+
|----------|----------|
|
|
122
|
+
| Critical | Props changed + >10 parent components |
|
|
123
|
+
| High | Props changed OR >10 parent components |
|
|
124
|
+
| Medium | 3-10 parent components |
|
|
125
|
+
| Low | <3 parent components |
|
|
126
|
+
|
|
127
|
+
## 7. Component Detection Patterns
|
|
128
|
+
|
|
129
|
+
### React Functional Components
|
|
130
|
+
```javascript
|
|
131
|
+
// Named function
|
|
132
|
+
function Button({ label }) {
|
|
133
|
+
return <button>{label}</button>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Arrow function
|
|
137
|
+
const Button = ({ label }) => <button>{label}</button>;
|
|
138
|
+
|
|
139
|
+
// React. FC type
|
|
140
|
+
const Button: React.FC<ButtonProps> = ({ label }) => { };
|
|
141
|
+
|
|
142
|
+
// forwardRef
|
|
143
|
+
const Button = forwardRef((props, ref) => { });
|
|
144
|
+
|
|
145
|
+
// memo
|
|
146
|
+
const Button = memo(({ label }) => { });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### React Class Components
|
|
150
|
+
```javascript
|
|
151
|
+
class Button extends React.Component { }
|
|
152
|
+
class Button extends Component { }
|
|
153
|
+
class Button extends React.PureComponent { }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Vue Components
|
|
157
|
+
```vue
|
|
158
|
+
<!-- Options API -->
|
|
159
|
+
<script>
|
|
160
|
+
export default {
|
|
161
|
+
name: 'Button',
|
|
162
|
+
props: { label: String }
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<!-- Composition API -->
|
|
167
|
+
<script setup>
|
|
168
|
+
defineProps({ label: String })
|
|
169
|
+
</script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Angular Components
|
|
173
|
+
```typescript
|
|
174
|
+
@Component({
|
|
175
|
+
selector: 'app-button',
|
|
176
|
+
template: '<button>{{label}}</button>'
|
|
177
|
+
})
|
|
178
|
+
export class ButtonComponent {
|
|
179
|
+
@Input() label: string;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 8. Test Scenarios
|
|
184
|
+
|
|
185
|
+
### Scenario 1: Direct Component Change
|
|
186
|
+
- **Given**: A component's JSX is modified
|
|
187
|
+
- **When**: Analysis runs
|
|
188
|
+
- **Then**: Component is detected with its parent components
|
|
189
|
+
|
|
190
|
+
### Scenario 2: Props Interface Change
|
|
191
|
+
- **Given**: A component's Props interface is modified
|
|
192
|
+
- **When**: Analysis runs
|
|
193
|
+
- **Then**: `propsChanged` is true and severity is elevated
|
|
194
|
+
|
|
195
|
+
### Scenario 3: Shared Component Change
|
|
196
|
+
- **Given**: A shared component (used by 15+ parents) is modified
|
|
197
|
+
- **When**: Analysis runs
|
|
198
|
+
- **Then**: All parent components are listed, severity is high
|
|
199
|
+
|
|
200
|
+
### Scenario 4: Isolated Component Change
|
|
201
|
+
- **Given**: A component with no parents is modified
|
|
202
|
+
- **When**: Analysis runs
|
|
203
|
+
- **Then**: Component is detected with empty parent list
|
|
204
|
+
|
|
205
|
+
### Scenario 5: Style-Only Change
|
|
206
|
+
- **Given**: Only CSS/styles in a component file change
|
|
207
|
+
- **When**: Analysis runs
|
|
208
|
+
- **Then**: Component is detected with low severity
|
|
209
|
+
|
|
210
|
+
## 9. Edge Cases
|
|
211
|
+
|
|
212
|
+
| Case | Expected Behavior |
|
|
213
|
+
|------|-------------------|
|
|
214
|
+
| Higher-order components | Detect both wrapper and wrapped |
|
|
215
|
+
| Render props | Detect component, not render function |
|
|
216
|
+
| Context providers | Detect as component |
|
|
217
|
+
| CSS-only files | Skip, not a component |
|
|
218
|
+
| Test files | Skip (excluded by pattern) |
|
|
219
|
+
| Storybook files | Skip (excluded by pattern) |
|
|
220
|
+
|
|
221
|
+
## 10. Example
|
|
222
|
+
|
|
223
|
+
### Input
|
|
224
|
+
```javascript
|
|
225
|
+
// Changed file: src/components/Button/Button.jsx
|
|
226
|
+
// Modified lines: 5-8
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @typedef {Object} ButtonProps
|
|
230
|
+
* @property {string} label
|
|
231
|
+
* @property {string} variant // Line 5 - ADDED
|
|
232
|
+
* @property {() => void} onClick
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
function Button({ label, variant, onClick }) { // Line 8 - CHANGED
|
|
236
|
+
return (
|
|
237
|
+
<button className={variant} onClick={onClick}>
|
|
238
|
+
{label}
|
|
239
|
+
</button>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Expected Output
|
|
245
|
+
```javascript
|
|
246
|
+
{
|
|
247
|
+
component: [
|
|
248
|
+
{
|
|
249
|
+
componentName: "Button",
|
|
250
|
+
filePath: "src/components/Button/Button.jsx",
|
|
251
|
+
componentType: "react-functional",
|
|
252
|
+
parentComponents: [
|
|
253
|
+
{ name: "Header", filePath: "src/components/Header. jsx", usageCount: 2 },
|
|
254
|
+
{ name: "Modal", filePath: "src/components/Modal.jsx", usageCount: 1 },
|
|
255
|
+
{ name: "Form", filePath: "src/components/Form.jsx", usageCount: 3 }
|
|
256
|
+
],
|
|
257
|
+
propsChanged: true,
|
|
258
|
+
changeSource: { path: "src/components/Button/Button.jsx", ... },
|
|
259
|
+
severity: "medium"
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
```
|