abapgit-agent 1.14.4 → 1.15.0
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/abap/CLAUDE.md +55 -8
- package/abap/guidelines/abapgit-fugr.md +159 -0
- package/abap/guidelines/abapgit-xml-only.md +397 -0
- package/abap/guidelines/abapgit.md +29 -154
- package/abap/guidelines/abaplint-local.md +1 -1
- package/abap/guidelines/abaplint.md +1 -1
- package/abap/guidelines/branch-workflow.md +1 -1
- package/abap/guidelines/cds-testing.md +1 -1
- package/abap/guidelines/common-errors.md +1 -1
- package/abap/guidelines/debug-dump.md +1 -1
- package/abap/guidelines/debug-session.md +77 -56
- package/abap/guidelines/object-creation.md +1 -1
- package/abap/guidelines/probe-poc.md +1 -1
- package/abap/guidelines/run-probe-classes.md +1 -1
- package/abap/guidelines/unit-testable-code.md +1 -43
- package/abap/guidelines/workflow-detailed.md +1 -1
- package/package.json +1 -1
- package/src/commands/debug.js +49 -7
- package/src/commands/view.js +115 -6
- package/src/utils/abap-reference.js +2 -0
package/src/commands/debug.js
CHANGED
|
@@ -60,6 +60,23 @@ function hasFlag(args, flag) {
|
|
|
60
60
|
return args.includes(flag);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Valid class include types for --include flag.
|
|
65
|
+
* User-facing names mirror the abapGit file suffixes (.clas.<name>.abap).
|
|
66
|
+
* Maps user-facing name → ADT /includes/<type> path segment.
|
|
67
|
+
* Verified by live ADT testing: breakpoints accepted for all three.
|
|
68
|
+
* testclasses → testclasses → CCAU (unit test class file)
|
|
69
|
+
* locals_imp → implementations → CCIMP (local class implementations)
|
|
70
|
+
* locals_def → definitions → CCDEF (local class definitions)
|
|
71
|
+
*/
|
|
72
|
+
const CLASS_INCLUDE_TYPES = new Set(['testclasses', 'locals_imp', 'locals_def']);
|
|
73
|
+
|
|
74
|
+
const INCLUDE_TYPE_TO_ADT = {
|
|
75
|
+
testclasses: 'testclasses',
|
|
76
|
+
locals_imp: 'implementations',
|
|
77
|
+
locals_def: 'definitions',
|
|
78
|
+
};
|
|
79
|
+
|
|
63
80
|
/**
|
|
64
81
|
* Determine ADT object URI from object name (class/interface vs program vs include).
|
|
65
82
|
* Must use /source/main suffix for classes — verified by live testing: ADT
|
|
@@ -71,13 +88,30 @@ function hasFlag(args, flag) {
|
|
|
71
88
|
* Class method includes are named <ClassName padded to 30 chars with '='>CM<suffix>
|
|
72
89
|
* e.g. ZCL_ABGAGT_AGENT=============CM00D
|
|
73
90
|
* These must be routed to the programs/includes ADT endpoint.
|
|
91
|
+
*
|
|
92
|
+
* FUGR source includes follow L<group><suffix> naming (e.g. LSUSRU04, LSUSRTOP,
|
|
93
|
+
* LSUSRF10, L_ABAU01). They start with 'L' and are routed to programs/includes.
|
|
94
|
+
* Customer-namespace programs always start with Z/Y, so this branch is safe.
|
|
95
|
+
*
|
|
96
|
+
* When includeType is supplied (testclasses|locals_imp|locals_def),
|
|
97
|
+
* the URI targets the sub-include of the class instead of /source/main.
|
|
98
|
+
* Line numbers are then section-local (from the .clas.<file>.abap file).
|
|
74
99
|
*/
|
|
75
|
-
function objectUri(name) {
|
|
100
|
+
function objectUri(name, includeType) {
|
|
76
101
|
const upper = (name || '').toUpperCase();
|
|
77
102
|
const lower = upper.toLowerCase();
|
|
78
103
|
if (/^[ZY](CL|IF)_/.test(upper) || /^(ZCL|ZIF|YCL|YIF)/.test(upper)) {
|
|
104
|
+
if (includeType && CLASS_INCLUDE_TYPES.has(includeType)) {
|
|
105
|
+
const adtType = INCLUDE_TYPE_TO_ADT[includeType];
|
|
106
|
+
return `/sap/bc/adt/oo/classes/${lower}/includes/${adtType}`;
|
|
107
|
+
}
|
|
79
108
|
return `/sap/bc/adt/oo/classes/${lower}/source/main`;
|
|
80
109
|
}
|
|
110
|
+
// FUGR source includes: L<group>U<NN>, L<group>TOP, L<group>F<NN>, etc.
|
|
111
|
+
// All start with 'L'. Customer Z/Y programs never start with 'L'.
|
|
112
|
+
if (/^L/.test(upper)) {
|
|
113
|
+
return `/sap/bc/adt/programs/includes/${lower}`;
|
|
114
|
+
}
|
|
81
115
|
return `/sap/bc/adt/programs/programs/${lower}`;
|
|
82
116
|
}
|
|
83
117
|
|
|
@@ -247,11 +281,18 @@ function parseBreakpointToken(token) {
|
|
|
247
281
|
}
|
|
248
282
|
|
|
249
283
|
async function cmdSet(args, config, adt) {
|
|
250
|
-
const objectName
|
|
251
|
-
const lineRaw
|
|
252
|
-
const filesArg
|
|
253
|
-
const objectsArg
|
|
254
|
-
const
|
|
284
|
+
const objectName = val(args, '--object');
|
|
285
|
+
const lineRaw = val(args, '--line');
|
|
286
|
+
const filesArg = val(args, '--files');
|
|
287
|
+
const objectsArg = val(args, '--objects');
|
|
288
|
+
const includeType = val(args, '--include');
|
|
289
|
+
const jsonOutput = hasFlag(args, '--json');
|
|
290
|
+
|
|
291
|
+
// Validate --include if supplied
|
|
292
|
+
if (includeType && !CLASS_INCLUDE_TYPES.has(includeType)) {
|
|
293
|
+
console.error(` Error: --include must be one of: ${[...CLASS_INCLUDE_TYPES].join(', ')}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
255
296
|
|
|
256
297
|
// Collect all breakpoints to add from every accepted input form
|
|
257
298
|
const toAdd = []; // [{ name, line }]
|
|
@@ -304,6 +345,7 @@ async function cmdSet(args, config, adt) {
|
|
|
304
345
|
console.error(' debug set --files src/zcl_my_class.clas.abap:42');
|
|
305
346
|
console.error(' debug set --objects ZCL_MY_CLASS:42');
|
|
306
347
|
console.error(' debug set --object ZCL_MY_CLASS --line 42');
|
|
348
|
+
console.error(' debug set --objects ZCL_MY_CLASS:16 --include testclasses');
|
|
307
349
|
process.exit(1);
|
|
308
350
|
}
|
|
309
351
|
|
|
@@ -312,7 +354,7 @@ async function cmdSet(args, config, adt) {
|
|
|
312
354
|
const added = [];
|
|
313
355
|
|
|
314
356
|
for (const { name, line } of toAdd) {
|
|
315
|
-
const uri = objectUri(name);
|
|
357
|
+
const uri = objectUri(name, includeType);
|
|
316
358
|
const objUpper = name.toUpperCase();
|
|
317
359
|
// Skip if an identical breakpoint already exists
|
|
318
360
|
if (existing.some(bp => bp.object === objUpper && bp.line === line)) {
|
package/src/commands/view.js
CHANGED
|
@@ -145,18 +145,38 @@ async function computeGlobalStarts(objName, sections, config) {
|
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
147
|
* Given the lines of a CM method section, return the 0-based index of the
|
|
148
|
-
* first "executable" line — i.e. skip METHOD, blank lines,
|
|
149
|
-
* declaration lines (DATA/FINAL/TYPES/CONSTANTS/CLASS-DATA)
|
|
148
|
+
* first "executable" line — i.e. skip METHOD, blank lines, comment lines,
|
|
149
|
+
* and declaration lines (DATA/FINAL/TYPES/CONSTANTS/CLASS-DATA), including
|
|
150
|
+
* multi-line DATA: blocks whose continuation lines end with a period.
|
|
150
151
|
* Returns 0 if no better line is found (falls back to METHOD statement).
|
|
151
152
|
*/
|
|
152
153
|
function findFirstExecutableLine(lines) {
|
|
153
|
-
const declPattern = /^\s*(data|final|types|constants|class-data)[\s:]/i;
|
|
154
|
+
const declPattern = /^\s*(data|final|types|constants|class-data)[\s:(]/i;
|
|
154
155
|
const methodPattern = /^\s*method\s+/i;
|
|
156
|
+
const commentPattern = /^\s*[*"]/;
|
|
157
|
+
// Program-level header/declaration keywords that are not executable statements
|
|
158
|
+
const progDeclPattern = /^\s*(report|program|parameters|tables|selection-screen|select-options|class-pool|function-pool|interface-pool|type-pool|include)\b/i;
|
|
159
|
+
let inDeclBlock = false; // true while inside a multi-line DATA:/TYPES:/PARAMETERS: block
|
|
155
160
|
for (let i = 0; i < lines.length; i++) {
|
|
156
161
|
const trimmed = lines[i].trim();
|
|
162
|
+
if (inDeclBlock) {
|
|
163
|
+
// continuation line — skip until the block closes with a period
|
|
164
|
+
if (trimmed.endsWith('.')) inDeclBlock = false;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
157
167
|
if (!trimmed) continue; // blank line
|
|
158
168
|
if (methodPattern.test(trimmed)) continue; // METHOD statement itself
|
|
159
|
-
if (
|
|
169
|
+
if (commentPattern.test(trimmed)) continue; // comment line
|
|
170
|
+
if (declPattern.test(trimmed)) {
|
|
171
|
+
// Multi-line block (DATA: ...,\n ...) stays open until period
|
|
172
|
+
if (!trimmed.endsWith('.')) inDeclBlock = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (progDeclPattern.test(trimmed)) {
|
|
176
|
+
// Multi-line block (PARAMETERS: ...,\n ...) stays open until period
|
|
177
|
+
if (!trimmed.endsWith('.')) inDeclBlock = true;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
160
180
|
return i;
|
|
161
181
|
}
|
|
162
182
|
return 0;
|
|
@@ -188,6 +208,15 @@ module.exports = {
|
|
|
188
208
|
const jsonOutput = args.includes('--json');
|
|
189
209
|
const fullMode = args.includes('--full');
|
|
190
210
|
const linesMode = args.includes('--lines');
|
|
211
|
+
const fmArgIndex = args.indexOf('--fm');
|
|
212
|
+
const fmName = fmArgIndex !== -1 && fmArgIndex + 1 < args.length
|
|
213
|
+
? args[fmArgIndex + 1].toUpperCase()
|
|
214
|
+
: null;
|
|
215
|
+
|
|
216
|
+
if (fmName && !fullMode) {
|
|
217
|
+
console.error(' Error: --fm requires --full');
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
191
220
|
|
|
192
221
|
console.log(`\n Viewing ${objects.length} object(s)`);
|
|
193
222
|
|
|
@@ -207,6 +236,10 @@ module.exports = {
|
|
|
207
236
|
data.full = true;
|
|
208
237
|
}
|
|
209
238
|
|
|
239
|
+
if (fmName) {
|
|
240
|
+
data.fm = fmName;
|
|
241
|
+
}
|
|
242
|
+
|
|
210
243
|
const result = await http.post('/sap/bc/z_abapgit_agent/view', data, { csrfToken });
|
|
211
244
|
|
|
212
245
|
// Handle uppercase keys from ABAP
|
|
@@ -277,11 +310,22 @@ module.exports = {
|
|
|
277
310
|
const file = section.FILE || section.file || '';
|
|
278
311
|
const lines = section.LINES || section.lines || [];
|
|
279
312
|
const isCmSection = suffix.startsWith('CM') && methodName;
|
|
313
|
+
const isFugrFmSection = !isCmSection && !!methodName;
|
|
280
314
|
|
|
281
315
|
if (linesMode) {
|
|
282
316
|
// --full --lines: dual line numbers (G [N]) for debugging
|
|
283
317
|
const globalStart = section.globalStart || 0;
|
|
284
318
|
|
|
319
|
+
// Map abapGit file suffix to the --include flag value used in debug set hints.
|
|
320
|
+
// User-facing names mirror the abapGit file suffixes (.clas.<name>.abap).
|
|
321
|
+
// Verified by live ADT testing: /includes/<adtType> endpoint accepts BPs.
|
|
322
|
+
const INCLUDE_FLAG_VALUE = {
|
|
323
|
+
testclasses: 'testclasses',
|
|
324
|
+
locals_imp: 'locals_imp',
|
|
325
|
+
locals_def: 'locals_def',
|
|
326
|
+
};
|
|
327
|
+
const includeFlag = INCLUDE_FLAG_VALUE[file] || null;
|
|
328
|
+
|
|
285
329
|
if (isCmSection) {
|
|
286
330
|
let bpHint;
|
|
287
331
|
if (globalStart) {
|
|
@@ -292,13 +336,52 @@ module.exports = {
|
|
|
292
336
|
bpHint = `debug set --objects ${objName}:<global_line>`;
|
|
293
337
|
}
|
|
294
338
|
console.log(` * ---- Method: ${methodName} (${suffix}) — breakpoint: ${bpHint} ----`);
|
|
339
|
+
} else if (isFugrFmSection) {
|
|
340
|
+
// Find first executable line: skip FUNCTION header, comments, blanks,
|
|
341
|
+
// and declaration blocks (DATA:, CONSTANTS:, TYPES:, etc.)
|
|
342
|
+
const declPat = /^\s*(data|final|types|constants|class-data)[\s:(]/i;
|
|
343
|
+
let firstExecLine = 1;
|
|
344
|
+
let inDecl = false;
|
|
345
|
+
for (let li = 0; li < lines.length; li++) {
|
|
346
|
+
const t = lines[li].trim();
|
|
347
|
+
if (inDecl) {
|
|
348
|
+
if (t.endsWith('.')) inDecl = false;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (!t) continue;
|
|
352
|
+
if (/^\*/.test(t)) continue;
|
|
353
|
+
if (/^"/.test(t)) continue;
|
|
354
|
+
if (/^function\s+/i.test(t)) continue;
|
|
355
|
+
if (declPat.test(t)) {
|
|
356
|
+
if (!t.endsWith('.')) inDecl = true;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
firstExecLine = li + 1;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
const bpHint = `debug set --objects ${suffix}:${firstExecLine}`;
|
|
363
|
+
console.log(` * ---- FM: ${methodName} (${suffix}) — breakpoint: ${bpHint} ----`);
|
|
295
364
|
} else if (file) {
|
|
296
365
|
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
297
366
|
} else if (suffix) {
|
|
298
|
-
|
|
367
|
+
// For program source sections, emit a breakpoint hint at the first executable line.
|
|
368
|
+
const isProgSection = suffix === 'PROG' || suffix === 'prog';
|
|
369
|
+
if (isProgSection) {
|
|
370
|
+
const execOffset = findFirstExecutableLine(lines);
|
|
371
|
+
const execLine = execOffset + 1; // 1-based
|
|
372
|
+
const bpHint = `debug set --objects ${objName}:${execLine}`;
|
|
373
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (${suffix}) — breakpoint: ${bpHint} ----`);
|
|
374
|
+
} else {
|
|
375
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (${suffix}) ----`);
|
|
376
|
+
}
|
|
299
377
|
}
|
|
300
378
|
|
|
301
379
|
let includeRelLine = 0;
|
|
380
|
+
// Track when we're inside a METHOD block in a sub-include section
|
|
381
|
+
// so we can emit a breakpoint hint at the first executable line.
|
|
382
|
+
let inSubMethod = false;
|
|
383
|
+
let subMethodName = '';
|
|
384
|
+
let subMethodStartLine = 0; // 1-based line of METHOD statement
|
|
302
385
|
for (const codeLine of lines) {
|
|
303
386
|
includeRelLine++;
|
|
304
387
|
const globalLine = globalStart ? globalStart + includeRelLine - 1 : 0;
|
|
@@ -306,8 +389,32 @@ module.exports = {
|
|
|
306
389
|
const gStr = globalLine ? String(globalLine).padStart(4) : ' ';
|
|
307
390
|
const iStr = String(includeRelLine).padStart(3);
|
|
308
391
|
console.log(` ${gStr} [${iStr}] ${codeLine}`);
|
|
392
|
+
} else if (isFugrFmSection) {
|
|
393
|
+
// FM include: line numbers are include-relative = ADT line numbers
|
|
394
|
+
const lStr = String(includeRelLine).padStart(4);
|
|
395
|
+
console.log(` ${lStr} ${codeLine}`);
|
|
309
396
|
} else {
|
|
310
|
-
|
|
397
|
+
// For sub-include sections with a known ADT include type,
|
|
398
|
+
// detect METHOD..ENDMETHOD blocks and emit breakpoint hints.
|
|
399
|
+
if (includeFlag) {
|
|
400
|
+
const trimmed = codeLine.trim();
|
|
401
|
+
if (!inSubMethod && /^method\s+/i.test(trimmed)) {
|
|
402
|
+
// Entering a new method — find first executable line offset
|
|
403
|
+
// by scanning ahead from this line
|
|
404
|
+
const mName = (trimmed.match(/^method\s+([\w~]+)/i) || [])[1] || '';
|
|
405
|
+
// Collect lines from this METHOD onwards to find exec offset
|
|
406
|
+
const remainingLines = lines.slice(includeRelLine - 1); // 0-based from current
|
|
407
|
+
const execOffset = findFirstExecutableLine(remainingLines);
|
|
408
|
+
const execLine = includeRelLine + execOffset; // section-local line
|
|
409
|
+
const bpHint = `debug set --objects ${objName}:${execLine} --include ${includeFlag}`;
|
|
410
|
+
console.log(` * ---- Method: ${mName.toUpperCase()} — breakpoint: ${bpHint} ----`);
|
|
411
|
+
inSubMethod = true;
|
|
412
|
+
subMethodName = mName;
|
|
413
|
+
} else if (inSubMethod && /^endmethod\s*\./i.test(codeLine.trim())) {
|
|
414
|
+
inSubMethod = false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const lStr = String(includeRelLine).padStart(4);
|
|
311
418
|
console.log(` ${lStr} ${codeLine}`);
|
|
312
419
|
}
|
|
313
420
|
}
|
|
@@ -315,6 +422,8 @@ module.exports = {
|
|
|
315
422
|
// --full (no --lines): clean source, no line numbers
|
|
316
423
|
if (isCmSection) {
|
|
317
424
|
console.log(` * ---- Method: ${methodName} (${suffix}) ----`);
|
|
425
|
+
} else if (isFugrFmSection) {
|
|
426
|
+
console.log(` * ---- FM: ${methodName} (${suffix}) ----`);
|
|
318
427
|
} else if (file) {
|
|
319
428
|
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
320
429
|
} else if (suffix) {
|
|
@@ -515,6 +515,8 @@ async function getTopic(topic) {
|
|
|
515
515
|
// Map topic to local guideline file
|
|
516
516
|
const guidelineMap = {
|
|
517
517
|
'abapgit': 'abapgit.md',
|
|
518
|
+
'abapgit-xml-only': 'abapgit-xml-only.md',
|
|
519
|
+
'abapgit-fugr': 'abapgit-fugr.md',
|
|
518
520
|
'xml': 'objects.md',
|
|
519
521
|
'objects': 'objects.md',
|
|
520
522
|
'naming': 'objects.md'
|