abapgit-agent 1.14.3 → 1.14.5

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 CHANGED
@@ -426,17 +426,34 @@ Never assume — wait for the user's answer before proceeding.
426
426
 
427
427
  **Finding the right line number for a breakpoint:**
428
428
 
429
- Use `view --full --lines` to get assembled-source global line numbers (the `G` column) these are the coordinates ADT accepts for breakpoints:
429
+ Use `view --full --lines` to get ready-to-use `debug set` commands per method:
430
430
 
431
431
  ```bash
432
432
  abapgit-agent view --objects ZCL_FOO --full --lines
433
433
  ```
434
434
 
435
- Output format: `G [N] code` where `G` = global line (use with `debug set`) and `[N]` = include-relative (for navigation only).
435
+ **Regular methods (CM\*):** output shows `G [N] code` the method header has the exact hint:
436
+ ```
437
+ * ---- Method: DO_SOMETHING (CM002) — breakpoint: debug set --objects ZCL_FOO:90 ----
438
+ 88 [ 1] METHOD do_something.
439
+ 89 [ 2] DATA lv_x TYPE i.
440
+ 90 [ 3] lv_x = 1.
441
+ ```
442
+ `G` = global assembled-source line (for `debug set`), `[N]` = include-relative (navigation only).
443
+
444
+ **Unit test methods (CCAU) and local class methods (CCIMP):** use `--include` flag with section-local line numbers:
445
+ ```
446
+ * ---- Method: SETUP — breakpoint: debug set --objects ZCL_FOO:12 --include testclasses ----
447
+ * ---- Method: ZIF_BAR~DO_IT — breakpoint: debug set --objects ZCL_FOO:5 --include locals_imp ----
448
+ ```
436
449
 
437
450
  ```bash
438
- # Example: METHOD do_something. starts at global line 87
439
- abapgit-agent debug set --objects ZCL_FOO:90 # set BP a few lines after METHOD statement
451
+ # Regular method:
452
+ abapgit-agent debug set --objects ZCL_FOO:90
453
+ # Unit test method:
454
+ abapgit-agent debug set --objects ZCL_FOO:12 --include testclasses
455
+ # Local class method:
456
+ abapgit-agent debug set --objects ZCL_FOO:5 --include locals_imp
440
457
  ```
441
458
 
442
459
  Minimal correct sequence:
@@ -284,6 +284,156 @@ abapGit's serializer **omits fields that have their default value**. Writing ext
284
284
 
285
285
  **Why this matters**: Missing `SHLPORIGIN` or wrong `MASK`/`DDTEXT` order causes a permanent diff between git and the system-serialized XML.
286
286
 
287
+ #### Text Tables and Foreign Keys
288
+
289
+ A **text table** stores translatable texts for another table (the "main table"). It shares the same key fields as the main table plus a language field (`SPRAS` with data element `SPRAS`, or `LANGU` with data element `LANGU`).
290
+
291
+ **How to recognise a text table relationship in the XML:**
292
+ - `<DD09L>` has `<UEBERSETZ>X</UEBERSETZ>` — marks this table as a text table
293
+ - `<DD08V>` has `<FRKART>TEXT</FRKART>` — marks the foreign key as a text-table relationship (ordinary foreign keys omit this field)
294
+
295
+ **Three sections required in the text table XML:**
296
+
297
+ | Section | Purpose |
298
+ |---|---|
299
+ | `DD03P_TABLE` | Field definitions — same key fields as main table + language field + text fields |
300
+ | `DD05M_TABLE` | Foreign key field mappings — one entry per key field of the main table (excluding the language field) |
301
+ | `DD08V_TABLE` | Foreign key relationship — one entry with `FRKART>TEXT` |
302
+
303
+ **`DD03P` rules for the text table:**
304
+ - First key field (`MANDT`) must have `<CHECKTABLE>` pointing to the main table and `<SHLPORIGIN>P</SHLPORIGIN>`
305
+ - Language field (`SPRAS` or `LANGU`) gets `<SHLPORIGIN>D</SHLPORIGIN>` (added by serializer after activation — omit when writing manually)
306
+
307
+ **`DD05M` rules:**
308
+ - `FIELDNAME` = the anchor field in the text table (typically `MANDT` — the first key field)
309
+ - `FORTABLE` = text table name
310
+ - `FORKEY` = key field name in the main table
311
+ - `CHECKFIELD` = same as `FORKEY`
312
+ - `CHECKTABLE` = main table name
313
+ - `PRIMPOS` = position sequence (0001, 0002, …)
314
+ - `DOMNAME` / `DATATYPE` = domain and type of the check field (omit `DOMNAME` if unknown — serializer fills it in)
315
+
316
+ **`DD08V` rules:**
317
+ - `FIELDNAME` = same anchor field as used in `DD05M` (typically `MANDT`)
318
+ - `CHECKTABLE` = main table name
319
+ - `FRKART` = `TEXT` (text table) or omit (ordinary foreign key)
320
+ - `CARD` = `CN` (n:1 cardinality)
321
+ - `CARDLEFT` = `1`
322
+
323
+ **Example — text table `ZMY_TABLE_T` for main table `ZMY_TABLE` (keys: MANDT, ID1, ID2):**
324
+
325
+ ```xml
326
+ <?xml version="1.0" encoding="utf-8"?>
327
+ <abapGit version="v1.0.0" serializer="LCL_OBJECT_TABL" serializer_version="v1.0.0">
328
+ <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
329
+ <asx:values>
330
+ <DD02V>
331
+ <TABNAME>ZMY_TABLE_T</TABNAME>
332
+ <DDLANGUAGE>E</DDLANGUAGE>
333
+ <TABCLASS>TRANSP</TABCLASS>
334
+ <CLIDEP>X</CLIDEP>
335
+ <DDTEXT>My Table: Texts</DDTEXT>
336
+ <CONTFLAG>C</CONTFLAG>
337
+ </DD02V>
338
+ <DD09L>
339
+ <TABNAME>ZMY_TABLE_T</TABNAME>
340
+ <AS4LOCAL>A</AS4LOCAL>
341
+ <TABKAT>0</TABKAT>
342
+ <TABART>APPL0</TABART>
343
+ <UEBERSETZ>X</UEBERSETZ>
344
+ <BUFALLOW>N</BUFALLOW>
345
+ </DD09L>
346
+ <DD03P_TABLE>
347
+ <DD03P>
348
+ <FIELDNAME>MANDT</FIELDNAME>
349
+ <KEYFLAG>X</KEYFLAG>
350
+ <ROLLNAME>MANDT</ROLLNAME>
351
+ <CHECKTABLE>ZMY_TABLE</CHECKTABLE>
352
+ <ADMINFIELD>0</ADMINFIELD>
353
+ <NOTNULL>X</NOTNULL>
354
+ <SHLPORIGIN>P</SHLPORIGIN>
355
+ <COMPTYPE>E</COMPTYPE>
356
+ </DD03P>
357
+ <DD03P>
358
+ <FIELDNAME>ID1</FIELDNAME>
359
+ <KEYFLAG>X</KEYFLAG>
360
+ <ROLLNAME>ZMY_ID1</ROLLNAME>
361
+ <ADMINFIELD>0</ADMINFIELD>
362
+ <NOTNULL>X</NOTNULL>
363
+ <COMPTYPE>E</COMPTYPE>
364
+ </DD03P>
365
+ <DD03P>
366
+ <FIELDNAME>ID2</FIELDNAME>
367
+ <KEYFLAG>X</KEYFLAG>
368
+ <ROLLNAME>ZMY_ID2</ROLLNAME>
369
+ <ADMINFIELD>0</ADMINFIELD>
370
+ <NOTNULL>X</NOTNULL>
371
+ <COMPTYPE>E</COMPTYPE>
372
+ </DD03P>
373
+ <DD03P>
374
+ <FIELDNAME>SPRAS</FIELDNAME>
375
+ <KEYFLAG>X</KEYFLAG>
376
+ <ROLLNAME>SPRAS</ROLLNAME>
377
+ <ADMINFIELD>0</ADMINFIELD>
378
+ <NOTNULL>X</NOTNULL>
379
+ <COMPTYPE>E</COMPTYPE>
380
+ </DD03P>
381
+ <DD03P>
382
+ <FIELDNAME>DESCRIPTION</FIELDNAME>
383
+ <ROLLNAME>ZMY_DESCRIPTION</ROLLNAME>
384
+ <ADMINFIELD>0</ADMINFIELD>
385
+ <COMPTYPE>E</COMPTYPE>
386
+ </DD03P>
387
+ </DD03P_TABLE>
388
+ <DD05M_TABLE>
389
+ <DD05M>
390
+ <FIELDNAME>MANDT</FIELDNAME>
391
+ <FORTABLE>ZMY_TABLE_T</FORTABLE>
392
+ <FORKEY>MANDT</FORKEY>
393
+ <CHECKTABLE>ZMY_TABLE</CHECKTABLE>
394
+ <CHECKFIELD>MANDT</CHECKFIELD>
395
+ <PRIMPOS>0001</PRIMPOS>
396
+ <DOMNAME>MANDT</DOMNAME>
397
+ <DATATYPE>CLNT</DATATYPE>
398
+ </DD05M>
399
+ <DD05M>
400
+ <FIELDNAME>MANDT</FIELDNAME>
401
+ <FORTABLE>ZMY_TABLE_T</FORTABLE>
402
+ <FORKEY>ID1</FORKEY>
403
+ <CHECKTABLE>ZMY_TABLE</CHECKTABLE>
404
+ <CHECKFIELD>ID1</CHECKFIELD>
405
+ <PRIMPOS>0002</PRIMPOS>
406
+ <DATATYPE>CHAR</DATATYPE>
407
+ </DD05M>
408
+ <DD05M>
409
+ <FIELDNAME>MANDT</FIELDNAME>
410
+ <FORTABLE>ZMY_TABLE_T</FORTABLE>
411
+ <FORKEY>ID2</FORKEY>
412
+ <CHECKTABLE>ZMY_TABLE</CHECKTABLE>
413
+ <CHECKFIELD>ID2</CHECKFIELD>
414
+ <PRIMPOS>0003</PRIMPOS>
415
+ <DATATYPE>CHAR</DATATYPE>
416
+ </DD05M>
417
+ </DD05M_TABLE>
418
+ <DD08V_TABLE>
419
+ <DD08V>
420
+ <FIELDNAME>MANDT</FIELDNAME>
421
+ <CHECKTABLE>ZMY_TABLE</CHECKTABLE>
422
+ <FRKART>TEXT</FRKART>
423
+ <CARD>CN</CARD>
424
+ <CARDLEFT>1</CARDLEFT>
425
+ </DD08V>
426
+ </DD08V_TABLE>
427
+ </asx:values>
428
+ </asx:abap>
429
+ </abapGit>
430
+ ```
431
+
432
+ **Key rules:**
433
+ - Always activate the main table **before** the text table — the foreign key check requires the main table to exist
434
+ - Language field can be `SPRAS` (data element `SPRAS`) or `LANGU` (data element `LANGU`) — both are recognised by the system
435
+ - For an **ordinary foreign key** (not a text table): same `DD05M`/`DD08V` structure, but omit `<FRKART>TEXT</FRKART>` from `DD08V`
436
+
287
437
  ---
288
438
 
289
439
  ### CDS View / View Entity (DDLS)
@@ -17,7 +17,7 @@ Use `debug` when:
17
17
 
18
18
  **Step 1 — set a breakpoint** on the first executable statement you want to inspect:
19
19
 
20
- Use `view --objects ZCL_MY_CLASS --full --lines` to see the full source with **both** global assembled-source line numbers and include-relative `[N]` numbers:
20
+ Use `view --objects ZCL_MY_CLASS --full --lines` to see the full source with line numbers and ready-to-use `debug set` commands:
21
21
 
22
22
  ```bash
23
23
  abapgit-agent view --objects ZCL_MY_CLASS --full --lines
@@ -25,16 +25,11 @@ abapgit-agent view --objects ZCL_MY_CLASS --full --lines
25
25
 
26
26
  > **Tip**: `view --full` (without `--lines`) shows the same full source as clean readable code without line numbers — useful for understanding logic. Add `--lines` when you need line numbers for breakpoints.
27
27
 
28
- Each line is shown as `G [N] code` where:
29
- - **G** = assembled-source global line → use with `debug set --objects CLASS:G` or `debug set --files src/cls.clas.abap:G`
30
- - **[N]** = include-relative (restarts at 1 per method) → for code navigation only, not for breakpoints
28
+ The output varies by section type:
31
29
 
32
- The method header shows the ready-to-use `debug set` command pointing to the first executable line:
30
+ **Regular methods (CM\* sections)** dual line numbers `G [N]` per line, with a `debug set` hint at the method header:
33
31
 
34
32
  ```
35
- 1 CLASS zcl_my_class DEFINITION.
36
- 2 PUBLIC SECTION.
37
- 3 ENDCLASS.
38
33
  * ---- Method: EXECUTE (CM002) — breakpoint: debug set --objects ZCL_MY_CLASS:9 ----
39
34
  7 [ 1] METHOD execute.
40
35
  8 [ 2] DATA lv_x TYPE i.
@@ -42,44 +37,69 @@ The method header shows the ready-to-use `debug set` command pointing to the fir
42
37
  10 [ 4] ENDMETHOD.
43
38
  ```
44
39
 
45
- **Two scenarios:**
40
+ - **G** = assembled-source global line → use with `debug set --objects CLASS:G` or `--files src/cls.clas.abap:G`
41
+ - **[N]** = include-relative (restarts at 1 per method) → for code navigation only, not for breakpoints
42
+
43
+ The hint already points to the first **executable** line, skipping `METHOD`, blank lines, comments, and all declaration forms (`DATA`, `DATA:`, `DATA(`).
44
+
45
+ **Two scenarios for CM* methods:**
46
46
 
47
47
  | Scenario | Command |
48
48
  |---|---|
49
49
  | Source available locally (your own classes) | `debug set --files src/zcl_my_class.clas.abap:9` |
50
50
  | No local source (abapGit library, SAP standard) | `debug set --objects ZCL_MY_CLASS:9` |
51
51
 
52
- Both use the same assembled-source global line number **G** shown in the output. To set a breakpoint at `lv_x = 1.` (global line 9):
53
52
  ```bash
54
- # With local file:
55
- abapgit-agent debug set --files src/zcl_my_class.clas.abap:9
56
- # Without local file:
57
53
  abapgit-agent debug set --objects ZCL_MY_CLASS:9
58
54
  abapgit-agent debug list # confirm it was registered
59
55
  ```
60
56
 
61
- > **Line number must point to an executable statement.** The method header hint already skips the `METHOD` line, blank lines, and `DATA`/`FINAL`/`TYPES`/`CONSTANTS` declarations automatically. Two cases still require manual attention when picking a line from the output:
62
- >
63
- > 1. **Comment lines** — lines starting with `"` are never executable. ADT silently rejects the breakpoint.
64
- > Pick the next non-comment line instead.
65
- > ```
66
- > 95 [ 70] " --- Conflict detection --- ← NOT valid (comment)
67
- > 96 [ 71] " Build remote file entries… ← NOT valid (comment)
68
- > 97 [ 73] DATA(lt_file_entries) = … ← valid ✅ (use global 97)
69
- > ```
70
- >
71
- > 2. **First line of a multi-line inline `DATA(x) = call(`** — the ABAP debugger treats the
72
- > `DATA(x) =` line as a declaration, not an executable step. Set the breakpoint on the
73
- > **next standalone executable statement** after the closing `).` instead.
74
- > ```
75
- > 100 [ 92] DATA(ls_checks) = prepare_deserialize_checks( ← NOT valid (inline decl)
76
- > 101 [ 93] it_files = it_files ← NOT valid (continuation)
77
- > 104 [ 96] io_repo_desc = lo_repo_desc1 ). ← NOT valid (continuation)
78
- > 106 [ 98] mo_repo->create_new_log( ). ← valid ✅ (use global 106)
79
- > ```
57
+ **Unit test methods (CCAU section)** and **local class methods (CCIMP section)** live in separate ADT includes they cannot be addressed by the assembled-source global line. Their sections show section-local line numbers with a `--include` hint per method:
58
+
59
+ ```
60
+ * ---- Section: Unit Test (from .clas.testclasses.abap) ----
61
+ * ---- Method: SETUP — breakpoint: debug set --objects ZCL_MY_CLASS:12 --include testclasses ----
62
+ 10 METHOD setup.
63
+ 11 DATA lv_x TYPE i.
64
+ 12 mo_cut = NEW #( ).
65
+ 13 ENDMETHOD.
66
+ * ---- Method: TEST_PULL_SUCCESS — breakpoint: debug set --objects ZCL_MY_CLASS:18 --include testclasses ----
67
+ 15 METHOD test_pull_success.
68
+ ...
69
+
70
+ * ---- Section: Local Implementations (from .clas.locals_imp.abap) ----
71
+ * ---- Method: ZIF_FOO~DO_SOMETHING breakpoint: debug set --objects ZCL_MY_CLASS:5 --include locals_imp ----
72
+ 3 METHOD zif_foo~do_something.
73
+ 4 DATA lv_x TYPE i.
74
+ 5 lv_x = iv_input.
75
+ 6 ENDMETHOD.
76
+ ```
77
+
78
+ The line numbers in these sections are **section-local** (same coordinate system as the `.clas.testclasses.abap` / `.clas.locals_imp.abap` file). Use the `--include` flag to target the correct ADT sub-include:
79
+
80
+ | Section | `--include` value |
81
+ |---|---|
82
+ | Unit Test (`.clas.testclasses.abap`) | `testclasses` |
83
+ | Local Implementations (`.clas.locals_imp.abap`) | `locals_imp` |
84
+ | Local Definitions (`.clas.locals_def.abap`) | `locals_def` |
85
+
86
+ ```bash
87
+ # Unit test method:
88
+ abapgit-agent debug set --objects ZCL_MY_CLASS:12 --include testclasses
89
+ # Local class method:
90
+ abapgit-agent debug set --objects ZCL_MY_CLASS:5 --include locals_imp
91
+ abapgit-agent debug list # confirm both were registered
92
+ ```
93
+
94
+ > **Line number must point to an executable statement.** The hints already skip `METHOD`, blank lines, comments (`"`, `*`), and all declaration forms (`DATA`, `DATA:`, `DATA(`). One case still requires manual attention:
80
95
  >
81
- > Other non-executable lines: blank lines, `METHOD`/`ENDMETHOD`, `DATA:` declarations,
82
- > `CLASS`/`ENDCLASS`. When in doubt, prefer a simple method call or assignment.
96
+ > **Multi-line inline `DATA(x) = call(`** — the ABAP debugger treats the whole expression as a declaration. Set the breakpoint on the **next standalone statement** after the closing `).`:
97
+ > ```
98
+ > 100 [ 1] DATA(ls_checks) = prepare_deserialize_checks( ← NOT valid (inline decl)
99
+ > 101 [ 2] it_files = it_files ← NOT valid (continuation)
100
+ > 104 [ 5] io_repo_desc = lo_repo_desc1 ). ← NOT valid (continuation)
101
+ > 106 [ 7] mo_repo->create_new_log( ). ← valid ✅ (use global 106)
102
+ > ```
83
103
 
84
104
  **Step 2 — attach and trigger**
85
105
 
@@ -259,22 +279,24 @@ HTTP Request
259
279
 
260
280
  ### Known Limitations and Planned Improvements
261
281
 
262
- The following issues were identified during a live debugging session (2026-03) and should be fixed to make future debugging easier:
282
+ The following issues were identified during live debugging sessions and resolved:
263
283
 
264
284
  #### ~~1. `view --full` global line numbers don't match ADT line numbers~~ ✅ Fixed
265
285
 
266
- **Fixed**: `view --full --lines` now shows dual line numbers per line: `G [N] code` where G is the assembled-source global line (usable directly with `--objects CLASS:G` or `--files src/cls.clas.abap:G`) and `[N]` is the include-relative counter for navigation. Method headers show the ready-to-use `debug set --objects CLASS:G` command pointing to the first executable statement. `view --full` (without `--lines`) shows the same full source as clean readable code without line numbers.
286
+ **Fixed**: `view --full --lines` now shows dual line numbers per line: `G [N] code` where G is the assembled-source global line and `[N]` is the include-relative counter. Method headers show the ready-to-use `debug set --objects CLASS:G` command pointing to the first executable statement (skipping `METHOD`, blank lines, comments, and all `DATA`/`DATA:`/`DATA(` declaration forms).
267
287
 
268
288
  Global line numbers are computed **client-side** in Node.js, not in ABAP:
269
289
  - **Own classes** (local `.clas.abap` file exists): reads the local file — guaranteed exact match with ADT
270
290
  - **Library classes** (no local file, e.g. abapGit): fetches assembled source from `/sap/bc/adt/oo/classes/<name>/source/main`
271
291
 
272
- Both strategies scan for `METHOD <name>.` as the first token on the line to find `global_start`. The method header hint automatically points to the **first executable statement** (skipping the `METHOD` line, blank lines, and `DATA`/`FINAL`/`TYPES`/`CONSTANTS` declarations) so it can be used directly without adjustment.
292
+ #### ~~2. No breakpoint support for unit test and local class methods~~ Fixed
293
+
294
+ **Fixed**: CCAU (unit test) and CCIMP (local class implementation) sections in `view --full --lines` now show per-method `debug set --objects CLASS:N --include <type>` hints. These use the `/includes/<type>` ADT sub-include URI which ADT accepts for CCAU/CCIMP includes. See Step 1 above for details.
273
295
 
274
- #### ~~2. Include-relative breakpoint form (`=====CMxxx:N`) not implemented in the CLI~~ ✅ Superseded
296
+ #### ~~3. Include-relative breakpoint form (`=====CMxxx:N`) not implemented in the CLI~~ ✅ Superseded
275
297
 
276
298
  **Superseded**: The `/programs/includes/` ADT endpoint was found to not accept breakpoints for OO class method includes — ADT only accepts the `/oo/classes/.../source/main` URI with assembled-source line numbers. The `=====CMxxx:N` approach was dropped. Instead, `view --full --lines` now provides the correct assembled-source global line number G directly, and both `--objects CLASS:G` and `--files src/cls.clas.abap:G` work reliably.
277
299
 
278
- #### ~~3. `stepContinue` re-attach pattern missing from docs~~ ✅ Fixed
300
+ #### ~~4. `stepContinue` re-attach pattern missing from docs~~ ✅ Fixed
279
301
 
280
302
  **Fixed**: Step 3 of the debug guide now documents the two possible return values of `step --type continue` and includes the re-attach pattern for when the program hits a second breakpoint instead of finishing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.14.3",
3
+ "version": "1.14.5",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -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,11 +88,19 @@ 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
+ * When includeType is supplied (testclasses|locals_imp|locals_def),
93
+ * the URI targets the sub-include of the class instead of /source/main.
94
+ * Line numbers are then section-local (from the .clas.<file>.abap file).
74
95
  */
75
- function objectUri(name) {
96
+ function objectUri(name, includeType) {
76
97
  const upper = (name || '').toUpperCase();
77
98
  const lower = upper.toLowerCase();
78
99
  if (/^[ZY](CL|IF)_/.test(upper) || /^(ZCL|ZIF|YCL|YIF)/.test(upper)) {
100
+ if (includeType && CLASS_INCLUDE_TYPES.has(includeType)) {
101
+ const adtType = INCLUDE_TYPE_TO_ADT[includeType];
102
+ return `/sap/bc/adt/oo/classes/${lower}/includes/${adtType}`;
103
+ }
79
104
  return `/sap/bc/adt/oo/classes/${lower}/source/main`;
80
105
  }
81
106
  return `/sap/bc/adt/programs/programs/${lower}`;
@@ -247,11 +272,18 @@ function parseBreakpointToken(token) {
247
272
  }
248
273
 
249
274
  async function cmdSet(args, config, adt) {
250
- const objectName = val(args, '--object');
251
- const lineRaw = val(args, '--line');
252
- const filesArg = val(args, '--files');
253
- const objectsArg = val(args, '--objects');
254
- const jsonOutput = hasFlag(args, '--json');
275
+ const objectName = val(args, '--object');
276
+ const lineRaw = val(args, '--line');
277
+ const filesArg = val(args, '--files');
278
+ const objectsArg = val(args, '--objects');
279
+ const includeType = val(args, '--include');
280
+ const jsonOutput = hasFlag(args, '--json');
281
+
282
+ // Validate --include if supplied
283
+ if (includeType && !CLASS_INCLUDE_TYPES.has(includeType)) {
284
+ console.error(` Error: --include must be one of: ${[...CLASS_INCLUDE_TYPES].join(', ')}`);
285
+ process.exit(1);
286
+ }
255
287
 
256
288
  // Collect all breakpoints to add from every accepted input form
257
289
  const toAdd = []; // [{ name, line }]
@@ -304,6 +336,7 @@ async function cmdSet(args, config, adt) {
304
336
  console.error(' debug set --files src/zcl_my_class.clas.abap:42');
305
337
  console.error(' debug set --objects ZCL_MY_CLASS:42');
306
338
  console.error(' debug set --object ZCL_MY_CLASS --line 42');
339
+ console.error(' debug set --objects ZCL_MY_CLASS:16 --include testclasses');
307
340
  process.exit(1);
308
341
  }
309
342
 
@@ -312,7 +345,7 @@ async function cmdSet(args, config, adt) {
312
345
  const added = [];
313
346
 
314
347
  for (const { name, line } of toAdd) {
315
- const uri = objectUri(name);
348
+ const uri = objectUri(name, includeType);
316
349
  const objUpper = name.toUpperCase();
317
350
  // Skip if an identical breakpoint already exists
318
351
  if (existing.some(bp => bp.object === objUpper && bp.line === line)) {
@@ -66,29 +66,44 @@ module.exports = {
66
66
  const allFiles = [...new Set([...abapFiles, ...depFiles])];
67
67
  cfg.global.files = allFiles.map(f => `/${f}`);
68
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
-
79
69
  const scopedConfig = '.abaplint-local.json';
80
70
  fs.writeFileSync(scopedConfig, JSON.stringify(cfg, null, 2));
81
71
 
82
72
  // ── Run abaplint ──────────────────────────────────────────────────────────
73
+ // Dep files are included in the scoped config so abaplint can resolve
74
+ // cross-references (e.g. implement_methods needs the interface source).
75
+ // When producing checkstyle output (CI mode), post-filter the XML to only
76
+ // keep <file> blocks for the originally changed files — suppressing any
77
+ // pre-existing issues in dependency files that were not part of this change.
83
78
  try {
84
- const formatArgs = outformat ? `--outformat ${outformat}` : '';
85
- const fileArgs = outfile ? `--outfile ${outfile}` : '';
86
- const result = spawnSync(
87
- `npx @abaplint/cli@latest ${scopedConfig} ${formatArgs} ${fileArgs}`,
88
- { stdio: 'inherit', shell: true }
89
- );
90
- if (result.status !== 0) {
91
- process.exitCode = result.status;
79
+ if (outformat === 'checkstyle') {
80
+ // Run to a temp file, filter, then write to the final destination.
81
+ const tempOut = '.abaplint-raw.xml';
82
+ const abapFilesSet = new Set(abapFiles.map(f => path.resolve(f)));
83
+ try {
84
+ const result = spawnSync(
85
+ `npx @abaplint/cli@latest ${scopedConfig} --outformat checkstyle --outfile ${tempOut}`,
86
+ { stdio: 'pipe', shell: true }
87
+ );
88
+ const raw = fs.existsSync(tempOut) ? fs.readFileSync(tempOut, 'utf8') : '<checkstyle version="8.0"/>';
89
+ const filtered = filterCheckstyleToFiles(raw, abapFilesSet);
90
+ if (outfile) {
91
+ fs.writeFileSync(outfile, filtered);
92
+ } else {
93
+ process.stdout.write(filtered);
94
+ }
95
+ const issueCount = (filtered.match(/<error /g) || []).length;
96
+ if (issueCount > 0) process.exitCode = 1;
97
+ } finally {
98
+ if (fs.existsSync(tempOut)) fs.unlinkSync(tempOut);
99
+ }
100
+ } else {
101
+ // Interactive: inherit stdio so abaplint's human-readable output flows through.
102
+ const result = spawnSync(
103
+ `npx @abaplint/cli@latest ${scopedConfig}`,
104
+ { stdio: 'inherit', shell: true }
105
+ );
106
+ if (result.status !== 0) process.exitCode = result.status;
92
107
  }
93
108
  } finally {
94
109
  fs.unlinkSync(scopedConfig);
@@ -178,7 +193,7 @@ function resolveDependencies(abapFiles, fileIndex) {
178
193
 
179
194
  // Patterns to extract referenced object names from ABAP source
180
195
  const patterns = [
181
- /^\s*INTERFACES\s+(\w+)\s*\./gim,
196
+ /^\s*INTERFACES:?\s+(\w+)\s*\./gim,
182
197
  /INHERITING\s+FROM\s+(\w+)/gim,
183
198
  /TYPE\s+REF\s+TO\s+(\w+)/gim,
184
199
  ];
@@ -238,6 +253,30 @@ function resolveDependencies(abapFiles, fileIndex) {
238
253
  return [...deps];
239
254
  }
240
255
 
256
+ /**
257
+ * Filter a checkstyle XML string to only include <file> blocks whose name
258
+ * attribute resolves to one of the files in the given Set of absolute paths.
259
+ * The outer <checkstyle> wrapper is preserved; the version attribute is kept.
260
+ */
261
+ function filterCheckstyleToFiles(xml, abapFilesSet) {
262
+ // Extract the opening <checkstyle ...> tag (preserves version= attribute).
263
+ const headerMatch = xml.match(/^[\s\S]*?(<checkstyle[^>]*>)/);
264
+ const header = headerMatch ? headerMatch[1] : '<checkstyle version="8.0">';
265
+
266
+ // Match each <file name="...">...</file> block (including self-closing).
267
+ const fileBlockRe = /<file\s+name="([^"]*)"[\s\S]*?<\/file>|<file\s+name="([^"]*)"\s*\/>/g;
268
+ const kept = [];
269
+ let match;
270
+ while ((match = fileBlockRe.exec(xml)) !== null) {
271
+ const filePath = match[1] || match[2];
272
+ if (abapFilesSet.has(path.resolve(filePath))) {
273
+ kept.push(match[0]);
274
+ }
275
+ }
276
+
277
+ return `<?xml version="1.0" encoding="UTF-8"?>\n${header}\n${kept.join('\n')}${kept.length ? '\n' : ''}</checkstyle>\n`;
278
+ }
279
+
241
280
  /**
242
281
  * Keep only files that look like ABAP source files
243
282
  * (name.type.abap or name.type.subtype.abap).
@@ -145,18 +145,31 @@ 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, and pure
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
+ let inDeclBlock = false; // true while inside a multi-line DATA:/TYPES: block
155
158
  for (let i = 0; i < lines.length; i++) {
156
159
  const trimmed = lines[i].trim();
160
+ if (inDeclBlock) {
161
+ // continuation line — skip until the block closes with a period
162
+ if (trimmed.endsWith('.')) inDeclBlock = false;
163
+ continue;
164
+ }
157
165
  if (!trimmed) continue; // blank line
158
166
  if (methodPattern.test(trimmed)) continue; // METHOD statement itself
159
- if (declPattern.test(trimmed)) continue; // declaration
167
+ if (commentPattern.test(trimmed)) continue; // comment line
168
+ if (declPattern.test(trimmed)) {
169
+ // Multi-line block (DATA: ...,\n ...) stays open until period
170
+ if (!trimmed.endsWith('.')) inDeclBlock = true;
171
+ continue;
172
+ }
160
173
  return i;
161
174
  }
162
175
  return 0;
@@ -282,6 +295,16 @@ module.exports = {
282
295
  // --full --lines: dual line numbers (G [N]) for debugging
283
296
  const globalStart = section.globalStart || 0;
284
297
 
298
+ // Map abapGit file suffix to the --include flag value used in debug set hints.
299
+ // User-facing names mirror the abapGit file suffixes (.clas.<name>.abap).
300
+ // Verified by live ADT testing: /includes/<adtType> endpoint accepts BPs.
301
+ const INCLUDE_FLAG_VALUE = {
302
+ testclasses: 'testclasses',
303
+ locals_imp: 'locals_imp',
304
+ locals_def: 'locals_def',
305
+ };
306
+ const includeFlag = INCLUDE_FLAG_VALUE[file] || null;
307
+
285
308
  if (isCmSection) {
286
309
  let bpHint;
287
310
  if (globalStart) {
@@ -299,6 +322,11 @@ module.exports = {
299
322
  }
300
323
 
301
324
  let includeRelLine = 0;
325
+ // Track when we're inside a METHOD block in a sub-include section
326
+ // so we can emit a breakpoint hint at the first executable line.
327
+ let inSubMethod = false;
328
+ let subMethodName = '';
329
+ let subMethodStartLine = 0; // 1-based line of METHOD statement
302
330
  for (const codeLine of lines) {
303
331
  includeRelLine++;
304
332
  const globalLine = globalStart ? globalStart + includeRelLine - 1 : 0;
@@ -307,7 +335,27 @@ module.exports = {
307
335
  const iStr = String(includeRelLine).padStart(3);
308
336
  console.log(` ${gStr} [${iStr}] ${codeLine}`);
309
337
  } else {
310
- const lStr = globalLine ? String(globalLine).padStart(4) : String(includeRelLine).padStart(4);
338
+ // For sub-include sections with a known ADT include type,
339
+ // detect METHOD..ENDMETHOD blocks and emit breakpoint hints.
340
+ if (includeFlag) {
341
+ const trimmed = codeLine.trim();
342
+ if (!inSubMethod && /^method\s+/i.test(trimmed)) {
343
+ // Entering a new method — find first executable line offset
344
+ // by scanning ahead from this line
345
+ const mName = (trimmed.match(/^method\s+([\w~]+)/i) || [])[1] || '';
346
+ // Collect lines from this METHOD onwards to find exec offset
347
+ const remainingLines = lines.slice(includeRelLine - 1); // 0-based from current
348
+ const execOffset = findFirstExecutableLine(remainingLines);
349
+ const execLine = includeRelLine + execOffset; // section-local line
350
+ const bpHint = `debug set --objects ${objName}:${execLine} --include ${includeFlag}`;
351
+ console.log(` * ---- Method: ${mName.toUpperCase()} — breakpoint: ${bpHint} ----`);
352
+ inSubMethod = true;
353
+ subMethodName = mName;
354
+ } else if (inSubMethod && /^endmethod\s*\./i.test(codeLine.trim())) {
355
+ inSubMethod = false;
356
+ }
357
+ }
358
+ const lStr = String(includeRelLine).padStart(4);
311
359
  console.log(` ${lStr} ${codeLine}`);
312
360
  }
313
361
  }