Skip to content

Commit 7667736

Browse files
committed
feat(service): integrate include resolution for cross-file Map tracking
1 parent 378ed32 commit 7667736

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

src/parsers/MapParser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export default class MapParser {
176176
// Target is outside functions, only consider global scope
177177
if (!declFunc) {
178178
const distance = targetLine - decl.line;
179-
if (distance < closestDistance) {
179+
if (distance <= closestDistance) {
180180
closestDistance = distance;
181181
closestDecl = decl;
182182
}

src/services/MapTrackingService.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
import MapParser from '../parsers/MapParser.js';
2+
import IncludeResolver from '../utils/IncludeResolver.js';
3+
import fs from 'fs';
24

35
/**
46
* Singleton service for tracking Map variables across workspace
57
*/
68
class MapTrackingService {
7-
constructor() {
9+
constructor(workspaceRoot = '', autoitIncludePaths = [], maxIncludeDepth = 3) {
810
if (MapTrackingService.instance) {
911
return MapTrackingService.instance;
1012
}
1113

1214
this.fileParsers = new Map(); // filePath -> MapParser
15+
this.includeResolver = new IncludeResolver(workspaceRoot, autoitIncludePaths, maxIncludeDepth);
16+
this.workspaceRoot = workspaceRoot;
1317
MapTrackingService.instance = this;
1418
}
1519

1620
/**
1721
* Get singleton instance
1822
* @returns {MapTrackingService}
1923
*/
20-
static getInstance() {
24+
static getInstance(workspaceRoot, autoitIncludePaths, maxIncludeDepth) {
2125
if (!MapTrackingService.instance) {
22-
MapTrackingService.instance = new MapTrackingService();
26+
MapTrackingService.instance = new MapTrackingService(
27+
workspaceRoot,
28+
autoitIncludePaths,
29+
maxIncludeDepth,
30+
);
2331
}
2432
return MapTrackingService.instance;
2533
}
@@ -64,6 +72,49 @@ class MapTrackingService {
6472

6573
return parser.getKeysForMapAtLine(mapName, line);
6674
}
75+
76+
/**
77+
* Get keys for a Map including keys from #include files
78+
* @param {string} filePath - Absolute file path
79+
* @param {string} mapName - Map variable name
80+
* @param {number} line - Line number
81+
* @returns {object} Object with directKeys and functionKeys arrays
82+
*/
83+
getKeysForMapWithIncludes(filePath, mapName, line) {
84+
// Get keys from current file
85+
const currentKeys = this.getKeysForMap(filePath, mapName, line);
86+
87+
// Get keys from included files
88+
const includedFiles = this.includeResolver.resolveAllIncludes(filePath);
89+
const allDirectKeys = new Set(currentKeys.directKeys);
90+
const allFunctionKeys = [...currentKeys.functionKeys];
91+
92+
for (const includedFile of includedFiles) {
93+
// Parse included file if not already cached
94+
if (!this.fileParsers.has(includedFile)) {
95+
try {
96+
if (fs.existsSync(includedFile)) {
97+
const source = fs.readFileSync(includedFile, 'utf8');
98+
this.updateFile(includedFile, source);
99+
}
100+
} catch (error) {
101+
// Gracefully handle read errors
102+
continue;
103+
}
104+
}
105+
106+
const includedKeys = this.getKeysForMap(includedFile, mapName, Infinity);
107+
108+
// Merge keys
109+
includedKeys.directKeys.forEach(key => allDirectKeys.add(key));
110+
allFunctionKeys.push(...includedKeys.functionKeys);
111+
}
112+
113+
return {
114+
directKeys: Array.from(allDirectKeys),
115+
functionKeys: allFunctionKeys,
116+
};
117+
}
67118
}
68119

69120
export default MapTrackingService;

test/services/MapTrackingService.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import MapTrackingService from '../../src/services/MapTrackingService.js';
2+
import fs from 'fs';
3+
4+
jest.mock('fs');
25

36
describe('MapTrackingService', () => {
47
let service;
@@ -64,4 +67,59 @@ $mUser.age = 30`,
6467
expect(keys2.directKeys).toHaveLength(0);
6568
});
6669
});
70+
71+
describe('getKeysForMapWithIncludes', () => {
72+
beforeEach(() => {
73+
jest.clearAllMocks();
74+
});
75+
76+
it('should merge keys from included files', () => {
77+
// Setup mocks
78+
fs.existsSync = jest.fn().mockReturnValue(true);
79+
fs.readFileSync = jest.fn(filePath => {
80+
// Normalize paths for comparison (handle Windows/Unix differences)
81+
const normalizedPath = filePath.replace(/\\/g, '/');
82+
if (normalizedPath.endsWith('/workspace/main.au3') || normalizedPath === '/workspace/main.au3') {
83+
return `#include "config.au3"
84+
Local $mApp[]
85+
$mApp.version = "1.0"`;
86+
}
87+
if (normalizedPath.endsWith('/workspace/config.au3') || normalizedPath === '/workspace/config.au3') {
88+
return `Global $mApp[]
89+
$mApp.name = "MyApp"`;
90+
}
91+
return '';
92+
});
93+
94+
const mainSource = `#include "config.au3"
95+
Local $mApp[]
96+
$mApp.version = "1.0"`;
97+
98+
service = MapTrackingService.getInstance('/workspace', []);
99+
service.clear();
100+
service.updateFile('/workspace/main.au3', mainSource);
101+
102+
const keys = service.getKeysForMapWithIncludes('/workspace/main.au3', '$mApp', 3);
103+
104+
expect(keys.directKeys).toContain('version');
105+
expect(keys.directKeys).toContain('name'); // From included file
106+
});
107+
108+
it('should handle missing included files gracefully', () => {
109+
fs.existsSync = jest.fn().mockReturnValue(false);
110+
111+
const source = `#include "missing.au3"
112+
Local $mData[]
113+
$mData.key = "value"`;
114+
115+
service = MapTrackingService.getInstance('/workspace', []);
116+
service.clear();
117+
service.updateFile('/workspace/test.au3', source);
118+
119+
const keys = service.getKeysForMapWithIncludes('/workspace/test.au3', '$mData', 3);
120+
121+
// Should still get keys from current file
122+
expect(keys.directKeys).toContain('key');
123+
});
124+
});
67125
});

0 commit comments

Comments
 (0)