abapgit-agent 1.14.1 → 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.1",
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,16 +198,31 @@ 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);
173
209
  queue.push(abapFile);
174
210
  }
211
+ // For interfaces, also include the canonical concrete implementation
212
+ // (zif_foo → zcl_foo) so rules like unused_variables can fully type-check.
213
+ if (suffix.endsWith('.intf')) {
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) {
218
+ deps.add(implFile);
219
+ if (implXml) deps.add(implXml);
220
+ if (!visited.has(implFile)) {
221
+ visited.add(implFile);
222
+ queue.push(implFile);
223
+ }
224
+ }
225
+ }
175
226
  break; // intf matched — don't also try clas
176
227
  }
177
228
  }
@@ -179,8 +230,9 @@ function resolveDependencies(abapFiles, abapDir) {
179
230
  }
180
231
 
181
232
  // Always include the XML companion of each scanned file
182
- const xmlCompanion = file.replace(/\.abap$/, '.xml');
183
- 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);
184
236
  }
185
237
 
186
238
  return [...deps];