abapgit-agent 1.14.2 → 1.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.14.2",
3
+ "version": "1.14.3",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -61,10 +61,21 @@ module.exports = {
61
61
  // Scope to changed files + their direct dependencies (interfaces, superclasses)
62
62
  // so abaplint can resolve cross-references without including the whole repo.
63
63
  const abapDir = cfg.global.files.replace(/\/\*\*.*$/, '').replace(/^\//, '') || 'abap';
64
- const depFiles = resolveDependencies(abapFiles, abapDir);
64
+ const fileIndex = buildFileIndex(abapDir);
65
+ const depFiles = resolveDependencies(abapFiles, fileIndex);
65
66
  const allFiles = [...new Set([...abapFiles, ...depFiles])];
66
67
  cfg.global.files = allFiles.map(f => `/${f}`);
67
68
 
69
+ // Exclude dependency files from reporting — they are included only for
70
+ // cross-reference resolution. Only the originally changed files are reported on.
71
+ const abapFilesSet = new Set(abapFiles);
72
+ const excludedDeps = depFiles
73
+ .filter(f => !abapFilesSet.has(f))
74
+ .map(f => `/${f}`);
75
+ if (excludedDeps.length > 0) {
76
+ cfg.global.exclude = [...new Set([...(cfg.global.exclude || []), ...excludedDeps])];
77
+ }
78
+
68
79
  const scopedConfig = '.abaplint-local.json';
69
80
  fs.writeFileSync(scopedConfig, JSON.stringify(cfg, null, 2));
70
81
 
@@ -123,6 +134,30 @@ function runGit(cmd) {
123
134
  }
124
135
  }
125
136
 
137
+ /**
138
+ * Build a map of basename → full path for all .abap and .xml files
139
+ * found recursively under abapDir. Used for dependency resolution so
140
+ * that projects with nested folder structures (e.g. src/module/pkg/foo.clas.abap)
141
+ * are handled correctly — not just flat abap/ layouts.
142
+ */
143
+ function buildFileIndex(abapDir) {
144
+ const index = new Map(); // basename (lowercase) → full path
145
+ function walk(dir) {
146
+ let entries;
147
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
148
+ for (const entry of entries) {
149
+ const full = path.join(dir, entry.name);
150
+ if (entry.isDirectory()) {
151
+ walk(full);
152
+ } else if (entry.name.endsWith('.abap') || entry.name.endsWith('.xml')) {
153
+ index.set(entry.name.toLowerCase(), full);
154
+ }
155
+ }
156
+ }
157
+ walk(abapDir);
158
+ return index;
159
+ }
160
+
126
161
  /**
127
162
  * Resolve direct dependencies of the given ABAP files by scanning their source
128
163
  * for interface/superclass/type references and mapping them to local files.
@@ -132,11 +167,12 @@ function runGit(cmd) {
132
167
  * INHERITING FROM <name> → <name>.clas.abap + <name>.clas.xml
133
168
  * TYPE REF TO <name> → <name>.intf.abap or <name>.clas.abap (whichever exists)
134
169
  *
135
- * Only resolves one level deep enough for abaplint to check the changed files.
170
+ * Uses a filename index built from a recursive walk of abapDir so that
171
+ * deeply nested project structures are handled correctly.
136
172
  * XML companion files are always included alongside their .abap counterpart
137
173
  * so xml_consistency checks can run.
138
174
  */
139
- function resolveDependencies(abapFiles, abapDir) {
175
+ function resolveDependencies(abapFiles, fileIndex) {
140
176
  const deps = new Set();
141
177
  const visited = new Set(abapFiles); // don't re-scan changed files as deps
142
178
 
@@ -162,11 +198,11 @@ function resolveDependencies(abapFiles, abapDir) {
162
198
  while ((match = pattern.exec(source)) !== null) {
163
199
  const name = match[1].toLowerCase();
164
200
  for (const suffix of [`${name}.intf`, `${name}.clas`]) {
165
- const abapFile = path.join(abapDir, `${suffix}.abap`);
166
- const xmlFile = path.join(abapDir, `${suffix}.xml`);
167
- if (fs.existsSync(abapFile)) {
201
+ const abapFile = fileIndex.get(`${suffix}.abap`);
202
+ const xmlFile = fileIndex.get(`${suffix}.xml`);
203
+ if (abapFile) {
168
204
  deps.add(abapFile);
169
- if (fs.existsSync(xmlFile)) deps.add(xmlFile);
205
+ if (xmlFile) deps.add(xmlFile);
170
206
  // Recurse into this dep if not yet visited
171
207
  if (!visited.has(abapFile)) {
172
208
  visited.add(abapFile);
@@ -175,12 +211,12 @@ function resolveDependencies(abapFiles, abapDir) {
175
211
  // For interfaces, also include the canonical concrete implementation
176
212
  // (zif_foo → zcl_foo) so rules like unused_variables can fully type-check.
177
213
  if (suffix.endsWith('.intf')) {
178
- const implName = name.replace(/^zif_/, 'zcl_');
179
- const implFile = path.join(abapDir, `${implName}.clas.abap`);
180
- const implXml = path.join(abapDir, `${implName}.clas.xml`);
181
- if (fs.existsSync(implFile)) {
214
+ const implName = name.replace(/^zif_/, 'zcl_');
215
+ const implFile = fileIndex.get(`${implName}.clas.abap`);
216
+ const implXml = fileIndex.get(`${implName}.clas.xml`);
217
+ if (implFile) {
182
218
  deps.add(implFile);
183
- if (fs.existsSync(implXml)) deps.add(implXml);
219
+ if (implXml) deps.add(implXml);
184
220
  if (!visited.has(implFile)) {
185
221
  visited.add(implFile);
186
222
  queue.push(implFile);
@@ -194,8 +230,9 @@ function resolveDependencies(abapFiles, abapDir) {
194
230
  }
195
231
 
196
232
  // Always include the XML companion of each scanned file
197
- const xmlCompanion = file.replace(/\.abap$/, '.xml');
198
- if (fs.existsSync(xmlCompanion)) deps.add(xmlCompanion);
233
+ const xmlBasename = path.basename(file).replace(/\.abap$/, '.xml').toLowerCase();
234
+ const xmlCompanion = fileIndex.get(xmlBasename);
235
+ if (xmlCompanion) deps.add(xmlCompanion);
199
236
  }
200
237
 
201
238
  return [...deps];