@sun-asterisk/impact-analyzer 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -163,9 +163,9 @@ jobs:
163
163
  analyze:
164
164
  runs-on: ubuntu-latest
165
165
  steps:
166
- - uses: actions/checkout@v3
166
+ - uses: actions/checkout@v4
167
167
  with:
168
- fetch-depth: 0
168
+ fetch-depth: 0 # Fetch full history
169
169
 
170
170
  - uses: actions/setup-node@v3
171
171
  with:
@@ -176,7 +176,6 @@ jobs:
176
176
  npx @sun-asterisk/impact-analyzer \
177
177
  --input=src \
178
178
  --base=origin/${{ github.base_ref }} \
179
- --head=${{ github.sha }} \
180
179
  --output=impact-report.md \
181
180
  --json=impact-report.json
182
181
 
@@ -200,11 +199,12 @@ jobs:
200
199
  impact-analysis:
201
200
  stage: test
202
201
  image: node:18
202
+ variables:
203
+ GIT_DEPTH: 0 # Fetch full history
203
204
  script:
204
205
  - npx @sun-asterisk/impact-analyzer
205
206
  --input=src
206
207
  --base=origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
207
- --head=$CI_COMMIT_SHA
208
208
  --output=impact-report.md
209
209
  --json=impact-report.json
210
210
  artifacts:
@@ -15,7 +15,7 @@ export function loadConfig(cli) {
15
15
 
16
16
  // Git references
17
17
  baseRef: cli.getArg('base', 'origin/main'),
18
- headRef: cli.getArg('head', 'HEAD'),
18
+ headRef: cli.getArg('head', ''), // Empty means current working directory
19
19
 
20
20
  // Analysis options
21
21
  maxDepth: parseInt(cli.getArg('max-depth', '3')),
@@ -38,9 +38,11 @@ function validateConfig(config) {
38
38
  throw new Error('Source directory (--input) is required');
39
39
  }
40
40
 
41
- if (!config.baseRef || !config.headRef) {
42
- throw new Error('Git references (--base and --head) are required');
41
+ if (!config.baseRef) {
42
+ throw new Error('Base git reference (--base) is required');
43
43
  }
44
+
45
+ // headRef is optional, defaults to working directory comparison
44
46
  }
45
47
 
46
48
  export const DEFAULT_CONFIG = {
@@ -11,7 +11,7 @@ export class GitUtils {
11
11
  /**
12
12
  * Get list of changed files between two refs
13
13
  * @param {string} baseRef - Base git reference
14
- * @param {string} headRef - Head git reference
14
+ * @param {string} headRef - Head git reference (empty = working directory)
15
15
  * @param {string} workDir - Working directory (optional, defaults to cwd)
16
16
  */
17
17
  static getChangedFiles(baseRef, headRef, workDir = null) {
@@ -23,10 +23,14 @@ export class GitUtils {
23
23
  throw new Error(`Directory does not exist: ${cwd}`);
24
24
  }
25
25
 
26
- const diffCommand = `git diff --name-status ${baseRef}...${headRef}`;
26
+ // If headRef is empty, compare baseRef with working directory
27
+ const diffCommand = headRef
28
+ ? `git diff --name-status ${baseRef}...${headRef}`
29
+ : `git diff --name-status ${baseRef}`;
30
+
27
31
  const diffOutput = execSync(diffCommand, {
28
32
  encoding: 'utf-8',
29
- cwd: cwd // FIXED: Execute git command in the source directory
33
+ cwd: cwd
30
34
  });
31
35
 
32
36
  const changedFiles = [];
@@ -48,7 +52,7 @@ export class GitUtils {
48
52
  console.error('Error getting changed files:', error.message);
49
53
  console.error(' Working directory:', workDir || process.cwd());
50
54
  console.error(' Base ref:', baseRef);
51
- console.error(' Head ref:', headRef);
55
+ console.error(' Head ref:', headRef || '(working directory)');
52
56
  return [];
53
57
  }
54
58
  }
@@ -77,7 +81,7 @@ export class GitUtils {
77
81
  * Get diff between two versions of a file
78
82
  * @param {string} filePath - File path relative to git root (e.g., 'src/modules/file.ts')
79
83
  * @param {string} baseRef - Base git reference
80
- * @param {string} headRef - Head git reference
84
+ * @param {string} headRef - Head git reference (empty = working directory)
81
85
  * @param {string} workDir - Working directory to run git from (optional)
82
86
  */
83
87
  static getFileDiff(filePath, baseRef, headRef, workDir = null) {
@@ -93,9 +97,10 @@ export class GitUtils {
93
97
  return '';
94
98
  }
95
99
 
96
- // filePath is already relative to git root (from git diff --name-status)
97
- // Execute git diff from git root
98
- const diffCommand = `git diff ${baseRef}...${headRef} -- ${filePath}`;
100
+ // If headRef is empty, compare with working directory
101
+ const diffCommand = headRef
102
+ ? `git diff ${baseRef}...${headRef} -- ${filePath}`
103
+ : `git diff ${baseRef} -- ${filePath}`;
99
104
 
100
105
  const result = execSync(diffCommand, {
101
106
  encoding: 'utf-8',
@@ -108,7 +113,7 @@ export class GitUtils {
108
113
  console.error('Error getting file diff:', error.message);
109
114
  console.error(' File:', filePath);
110
115
  console.error(' Base:', baseRef);
111
- console.error(' Head:', headRef);
116
+ console.error(' Head:', headRef || '(working directory)');
112
117
  console.error(' Working dir:', workDir);
113
118
  return '';
114
119
  }
@@ -448,89 +448,64 @@ export class MethodCallGraph {
448
448
 
449
449
  /**
450
450
  * Get changed methods from git diff
451
- * Detects both:
452
- * 1. Methods that contain changed lines
453
- * 2. Method calls that were added/removed
451
+ * Uses ts-morph AST to detect which specific methods contain changes
454
452
  */
455
453
  getChangedMethods(diff, filePath) {
456
- const changedMethods = new Set();
457
- const changedMethodCalls = new Set();
458
-
459
454
  if (!diff || !filePath) return [];
460
455
 
461
456
  const sourceFile = this.project.getSourceFile(filePath);
462
457
  if (!sourceFile) return [];
463
458
 
464
- const classes = sourceFile.getClasses();
465
- if (classes.length === 0) return [];
466
-
467
- // Parse diff to extract changed line numbers
459
+ // Extract changed line numbers from diff
468
460
  const changedLines = this.extractChangedLineNumbers(diff);
469
-
470
461
  if (changedLines.length === 0) return [];
471
462
 
472
- // 1. Find methods that contain the changed lines
463
+ const changedMethods = [];
464
+
465
+ // Get all classes in the file
466
+ const classes = sourceFile.getClasses();
467
+
473
468
  for (const classDecl of classes) {
474
469
  const className = classDecl.getName();
470
+ if (!className) continue;
471
+
475
472
  const methods = classDecl.getMethods();
476
-
473
+
477
474
  for (const method of methods) {
478
475
  const methodName = method.getName();
479
- const startLine = method.getStartLineNumber();
480
- const endLine = method.getEndLineNumber();
481
-
482
- // Check if any changed line falls within this method's range
483
- const hasChangedLine = changedLines.some(
484
- lineNum => lineNum >= startLine && lineNum <= endLine
485
- );
486
-
487
- if (hasChangedLine) {
488
- const fullName = `${className}.${methodName}`;
489
- changedMethods.add(fullName);
490
- }
491
- }
492
- }
493
-
494
- // 2. Detect method calls that were modified in the diff
495
- const diffLines = diff.split('\n');
496
-
497
- for (const line of diffLines) {
498
- // Only look at added/removed lines
499
- if (!line.startsWith('+') && !line.startsWith('-')) continue;
500
-
501
- const codeLine = line.substring(1).trim();
502
-
503
- // Pattern: object.methodName(...) or await object.methodName(...)
504
- const methodCallPattern = /(?:await\s+)?(\w+)\.(\w+)\s*\(/g;
505
- let match;
506
-
507
- while ((match = methodCallPattern.exec(codeLine)) !== null) {
508
- const objectName = match[1];
509
- const methodName = match[2];
510
476
 
511
- // Try to resolve the type of the object
512
- const resolvedType = this.resolveObjectType(objectName, sourceFile, classes);
513
-
514
- if (resolvedType) {
515
- const fullName = `${resolvedType}.${methodName}`;
516
- changedMethodCalls.add(fullName);
477
+ // Check if this specific method contains changes
478
+ if (this.methodContainsChanges(method, changedLines)) {
479
+ changedMethods.push({
480
+ className: className,
481
+ methodName: methodName,
482
+ file: filePath,
483
+ fullName: `${className}.${methodName}`
484
+ });
517
485
  }
518
486
  }
519
487
  }
520
-
521
- // Combine both: methods containing changes + method calls that changed
522
- const allChangedMethods = [...changedMethods, ...changedMethodCalls];
523
488
 
524
- // Convert to objects with metadata
525
- return [...new Set(allChangedMethods)].map(fullName => {
526
- const parts = fullName.split('.');
527
- return {
528
- className: parts[0],
529
- methodName: parts[1],
530
- file: filePath,
531
- fullName: fullName
532
- };
533
- });
489
+ return changedMethods;
490
+ }
491
+
492
+ /**
493
+ * Check if a method contains actual code changes using ts-morph AST
494
+ */
495
+ methodContainsChanges(method, changedLines) {
496
+ const body = method.getBody();
497
+ if (!body) return false;
498
+
499
+ // Get the method body block (excluding signature and decorators)
500
+ const bodyStartLine = body.getStartLineNumber();
501
+ const bodyEndLine = body.getEndLineNumber();
502
+
503
+ // Check if any changed line is within the method body
504
+ const hasChangesInBody = changedLines.some(
505
+ line => line >= bodyStartLine && line <= bodyEndLine
506
+ );
507
+
508
+ return hasChangesInBody;
534
509
  }
535
510
 
536
511
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/impact-analyzer",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Automated impact analysis for TypeScript/JavaScript projects",
5
5
  "main": "index.js",
6
6
  "type": "module",