abapgit-agent 1.13.6 → 1.14.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/README.md CHANGED
@@ -97,14 +97,17 @@ abapgit-agent health # Verify ABAP connection
97
97
 
98
98
  ```bash
99
99
  # Install dependencies
100
- cd abapgit-agent
101
100
  npm install
102
101
 
103
- # Run from package directory (auto-detects from git)
104
- node bin/abapgit-agent pull
102
+ # Run unit tests (no ABAP system needed)
103
+ npm test
105
104
 
106
- # Or use npm script
107
- npm run pull -- --url <git-url> --branch main
105
+ # Test a command manually
106
+ node bin/abapgit-agent --help
107
+ node bin/abapgit-agent syntax --files src/zcl_my_class.clas.abap
108
+
109
+ # Run integration tests against a real ABAP system (requires .abapGitAgent)
110
+ npm run test:integration
108
111
  ```
109
112
 
110
113
  ## Documentation
package/abap/CLAUDE.md CHANGED
@@ -211,6 +211,21 @@ abapgit-agent pull --files src/<intf_name>.intf.abap,src/<class_name>.clas.abap
211
211
 
212
212
  → See `guidelines/object-creation.md` — run: `abapgit-agent ref --topic object-creation`
213
213
 
214
+ **XML metadata when adding test classes:**
215
+
216
+ ```
217
+ Adding .clas.testclasses.abap to an existing class?
218
+ └── Update the .clas.xml → set WITH_UNIT_TESTS flag:
219
+ <clas:abapClassProperties ... abpUnitTestable="true" ... />
220
+ WITHOUT this flag, abapGit will not push/activate the test include.
221
+
222
+ Adding .clas.locals_def.abap (local type definitions)?
223
+ └── Update the .clas.xml → set CLSCCINCL flag:
224
+ <CLSCCINCL>X</CLSCCINCL>
225
+ ```
226
+
227
+ → For exact XML flag placement: `abapgit-agent ref --topic abapgit` (search "WITH_UNIT_TESTS")
228
+
214
229
  ---
215
230
 
216
231
  ### 6. Use `guide`, `ref`, `view` and `where` Commands to Learn About Unknown Classes/Methods
@@ -299,7 +314,47 @@ Use `CL_CDS_TEST_ENVIRONMENT` for unit tests that read CDS views.
299
314
 
300
315
  ---
301
316
 
302
- ### 8. Use `unit` Command for Unit Tests
317
+ ### 8. Writing and Running Unit Tests
318
+
319
+ #### Writing tests — use ABAP Test Double Framework by default
320
+
321
+ ```
322
+ ❌ WRONG: Write a manual test double class (ltd_mock_xxx) when the framework can do it
323
+ ✅ CORRECT: Use cl_abap_testdouble=>create / configure_call for all interface mocking
324
+ ```
325
+
326
+ **Decision — which double pattern to use:**
327
+
328
+ ```
329
+ Does the mock need stateful behaviour (e.g. count calls, vary results per call, complex logic)?
330
+ └── YES → manual test double class (ltd_mock_xxx DEFINITION FOR TESTING)
331
+ └── NO → ABAP Test Double Framework (cl_abap_testdouble=>create / configure_call)
332
+ This covers 90 %+ of cases — simple return value / exception mocking
333
+ ```
334
+
335
+ **ABAP Test Double Framework — quick pattern:**
336
+
337
+ ```abap
338
+ " 1. Create double (declare with interface type)
339
+ DATA lo_agent TYPE REF TO zif_abgagt_agent.
340
+ lo_agent ?= cl_abap_testdouble=>create( 'ZIF_ABGAGT_AGENT' ).
341
+
342
+ " 2. Configure return value
343
+ cl_abap_testdouble=>configure_call( lo_agent )->returning( ls_result ).
344
+ lo_agent->pull( iv_url = 'https://...' ). " registers config for these params
345
+
346
+ " 3. Inject and call
347
+ DATA(lo_cut) = NEW zcl_my_class( io_agent = lo_agent ).
348
+ DATA(ls_actual) = lo_cut->execute( ).
349
+ ```
350
+
351
+ → Full API reference (EXPORT params, exceptions, inherited methods, common mistakes):
352
+ `abapgit-agent ref --topic unit-testable-code`
353
+
354
+ → For class design rules (constructor injection, interfaces for dependencies):
355
+ `abapgit-agent ref --topic unit-testable-code`
356
+
357
+ #### Running tests — use `unit` command
303
358
 
304
359
  **Use `abapgit-agent unit` to run ABAP unit tests (AUnit).**
305
360
 
@@ -399,6 +454,56 @@ abapgit-agent debug step --type continue --json # 4. release
399
454
 
400
455
  ---
401
456
 
457
+ ### 12. abaplint — Static Analysis (Optional, Project-Controlled)
458
+
459
+ abaplint is **optional**. Only run it if `.abaplint.json` exists in the project root.
460
+ Each project defines its own rules — never assume which rules are active.
461
+
462
+ **Detection:**
463
+ ```bash
464
+ # Check whether this project uses abaplint
465
+ ls .abaplint.json 2>/dev/null && echo "abaplint enabled" || echo "no abaplint"
466
+ ```
467
+
468
+ **When to run:**
469
+
470
+ Run abaplint as step 4b — after `syntax`, before `git commit`:
471
+
472
+ ```bash
473
+ # Only if .abaplint.json exists
474
+ abapgit-agent lint
475
+ ```
476
+
477
+ Fix any reported issues, then commit.
478
+
479
+ **Before applying any quickfix:**
480
+
481
+ ```
482
+ ❌ WRONG: Accept abaplint quickfixes without checking
483
+ ✅ CORRECT: Run abapgit-agent ref --topic abaplint FIRST, then decide
484
+ ```
485
+
486
+ The `prefer_inline` quickfix is known to introduce a **silent type truncation bug**
487
+ when applied to variables that are later extended with `&&`. Read the guidelines
488
+ before applying it.
489
+
490
+ **When abaplint flags an issue you don't understand:**
491
+ ```bash
492
+ abapgit-agent ref --topic abaplint # bundled rule guidance
493
+ abapgit-agent ref "prefer_inline" # search for specific rule
494
+ abapgit-agent ref "no_inline" # search by keyword
495
+ ```
496
+
497
+ **Project-specific rule guidance:**
498
+
499
+ Projects can add their own abaplint notes to `guidelines/abaplint-local.md` in the
500
+ project repository. After running `abapgit-agent ref export`, the `ref` command
501
+ surfaces both bundled and project-specific guidance together.
502
+
503
+ → See `guidelines/abaplint.md` — run: `abapgit-agent ref --topic abaplint`
504
+
505
+ ---
506
+
402
507
  ## Development Workflow
403
508
 
404
509
  This project's workflow mode is configured in `.abapGitAgent` under `workflow.mode`.
@@ -434,6 +539,21 @@ See **AI Tool Guidelines** below for how to react to each setting.
434
539
  ### Branch Workflow (`"mode": "branch"`)
435
540
 
436
541
  Always work on feature branches. Before every `pull`: rebase to default branch. On completion: create PR with squash merge.
542
+
543
+ ```bash
544
+ git checkout main # or master/develop (auto-detected)
545
+ git pull origin main
546
+ git checkout -b feature/my-change
547
+ # edit your ABAP file (name from objects.local.md)
548
+ abapgit-agent syntax --files src/<name>.clas.abap
549
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
550
+ git add . && git commit -m "feat: description"
551
+ git push origin feature/my-change
552
+ git fetch origin main && git rebase origin/main
553
+ git push origin feature/my-change --force-with-lease
554
+ abapgit-agent pull --files src/<name>.clas.abap --sync-xml
555
+ ```
556
+
437
557
  → See `guidelines/branch-workflow.md` — run: `abapgit-agent ref --topic branch-workflow`
438
558
 
439
559
  ### Trunk Workflow (`"mode": "trunk"`)
@@ -445,6 +565,7 @@ git checkout main # or master/develop (auto-detected)
445
565
  git pull origin main
446
566
  # edit your ABAP file (name from objects.local.md)
447
567
  abapgit-agent syntax --files src/<name>.clas.abap
568
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
448
569
  git add . && git commit -m "feat: description"
449
570
  git push origin main
450
571
  abapgit-agent pull --files src/<name>.clas.abap --sync-xml
@@ -535,14 +656,17 @@ abapgit-agent pull --files src/<name>.clas.abap --sync-xml
535
656
  Modified ABAP files?
536
657
  ├─ CLAS/INTF/PROG/DDLS files?
537
658
  │ ├─ Independent files (no cross-dependencies)?
538
- │ │ └─ ✅ Use: syntax → commit → push → pull --sync-xml
659
+ │ │ └─ ✅ Use: syntax → [abaplint] → commit → push → pull --sync-xml
539
660
  │ └─ Dependent files (interface + class, class uses class)?
540
- │ └─ ✅ Use: skip syntax → commit → push → pull --sync-xml
661
+ │ └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --sync-xml
541
662
  └─ Other types (FUGR, TABL, STRU, DTEL, TTYP, etc.)?
542
663
  ├─ XML-only objects (TABL, STRU, DTEL, TTYP)?
543
- │ └─ ✅ Use: skip syntax → commit → push → pull --files abap/ztable.tabl.xml --sync-xml
664
+ │ └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --files abap/ztable.tabl.xml --sync-xml
544
665
  └─ FUGR and other complex objects?
545
- └─ ✅ Use: skip syntax → commit → push → pull --sync-xml → (if errors: inspect)
666
+ └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --sync-xml → (if errors: inspect)
667
+
668
+ [abaplint] = run abapgit-agent lint only if .abaplint.json exists in repo root
669
+ before applying any quickfix: run abapgit-agent ref --topic abaplint
546
670
  ```
547
671
 
548
672
  → For creating new objects (what files to write): `abapgit-agent ref --topic object-creation`
@@ -583,6 +707,7 @@ Detailed guidelines are available in the `guidelines/` folder:
583
707
  | `guidelines/workflow-detailed.md` | Development Workflow (Detailed) |
584
708
  | `guidelines/object-creation.md` | Object Creation (XML metadata, local classes) |
585
709
  | `guidelines/cds-testing.md` | CDS Testing (Test Double Framework) |
710
+ | `guidelines/abaplint.md` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
586
711
 
587
712
  These guidelines are automatically searched by the `ref` command.
588
713
 
@@ -0,0 +1,111 @@
1
+ ---
2
+ layout: default
3
+ title: abaplint Local Rules
4
+ nav_order: 18
5
+ parent: ABAP Coding Guidelines
6
+ grand_parent: ABAP Development
7
+ ---
8
+
9
+ # abaplint Local Rules — abapgit-agent project
10
+
11
+ **Searchable keywords**: naming, local_variable_names, method_parameter_names,
12
+ prefix, lv, lt, ls, lo, li, lx, iv, it, is, io, rv, rs, rt, ro
13
+
14
+ This project enforces **type-specific Hungarian notation** via `local_variable_names`
15
+ and `method_parameter_names` in `.abaplint.json`.
16
+
17
+ ---
18
+
19
+ ## Variable Naming — Required Prefixes
20
+
21
+ ### Local Variables (inside methods)
22
+
23
+ | Prefix | Type | Example |
24
+ |--------|------|---------|
25
+ | `lv_` | Scalar / value (i, string, char, …) | `lv_count TYPE i` |
26
+ | `lt_` | Internal table | `lt_files TYPE ty_files` |
27
+ | `ls_` | Structure | `ls_result TYPE ty_result` |
28
+ | `lo_` | Object reference | `lo_agent TYPE REF TO zcl_abgagt_agent` |
29
+ | `li_` | Interface reference | `li_repo TYPE REF TO zif_abapgit_repo` |
30
+ | `lx_` | Exception reference | `lx_error TYPE REF TO cx_static_check` |
31
+ | `lr_` | Data reference | `lr_data TYPE REF TO data` |
32
+ | `lc_` | Constant | `lc_max TYPE i VALUE 100` |
33
+
34
+ ### Field-Symbols (inside methods)
35
+
36
+ | Prefix | Example |
37
+ |--------|---------|
38
+ | `<lv_>`, `<lt_>`, `<ls_>`, `<lo_>`, `<li_>` | `FIELD-SYMBOLS <ls_item> TYPE ty_item` |
39
+ | `<comp>` | Allowed for generic component iteration |
40
+
41
+ ### Method Parameters
42
+
43
+ | Direction | Prefix | Type |
44
+ |-----------|--------|------|
45
+ | IMPORTING | `iv_` | scalar |
46
+ | IMPORTING | `it_` | table |
47
+ | IMPORTING | `is_` | structure |
48
+ | IMPORTING | `io_` | object ref |
49
+ | IMPORTING | `ii_` | interface ref |
50
+ | IMPORTING | `ix_` | exception ref |
51
+ | RETURNING | `rv_` | scalar |
52
+ | RETURNING | `rs_` | structure |
53
+ | RETURNING | `rt_` | table |
54
+ | RETURNING | `ro_` | object ref |
55
+ | RETURNING | `ri_` | interface ref |
56
+ | RETURNING | `rr_` | data ref |
57
+ | EXPORTING | `ev_`, `es_`, `et_` | scalar / structure / table |
58
+ | CHANGING | `cv_`, `cs_`, `ct_` | scalar / structure / table |
59
+
60
+ ---
61
+
62
+ ## Quick Reference
63
+
64
+ ```abap
65
+ " Local variables
66
+ DATA lv_name TYPE string.
67
+ DATA lt_files TYPE ty_files.
68
+ DATA ls_result TYPE ty_result.
69
+ DATA lo_agent TYPE REF TO zcl_abgagt_agent.
70
+ DATA li_repo TYPE REF TO zif_abapgit_repo.
71
+ DATA lx_error TYPE REF TO cx_static_check.
72
+ FIELD-SYMBOLS <ls_item> TYPE ty_item.
73
+
74
+ " Method signature
75
+ METHODS process
76
+ IMPORTING
77
+ iv_url TYPE string
78
+ it_files TYPE ty_files
79
+ is_config TYPE ty_config
80
+ io_agent TYPE REF TO zcl_abgagt_agent
81
+ ii_repo TYPE REF TO zif_abapgit_repo
82
+ RETURNING
83
+ VALUE(rv_result) TYPE string.
84
+
85
+ METHODS get_repo
86
+ RETURNING
87
+ VALUE(ro_repo) TYPE REF TO zcl_abgagt_agent.
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Rule: Never Use Generic `lv_` for Objects, Tables, or Structures
93
+
94
+ ```abap
95
+ " WRONG — abaplint will flag these
96
+ DATA lv_repo TYPE REF TO zcl_abgagt_agent. " use lo_
97
+ DATA lv_files TYPE ty_files. " use lt_
98
+ DATA lv_result TYPE ty_result. " use ls_
99
+
100
+ " CORRECT
101
+ DATA lo_repo TYPE REF TO zcl_abgagt_agent.
102
+ DATA lt_files TYPE ty_files.
103
+ DATA ls_result TYPE ty_result.
104
+ ```
105
+
106
+ ---
107
+
108
+ ## See Also
109
+
110
+ - `.abaplint.json` — rule definitions (`local_variable_names`, `method_parameter_names`)
111
+ - `guidelines/abaplint.md` — bundled guidance on `prefer_inline` trap
@@ -0,0 +1,224 @@
1
+ ---
2
+ layout: default
3
+ title: abaplint Rule Guidelines
4
+ nav_order: 17
5
+ parent: ABAP Coding Guidelines
6
+ grand_parent: ABAP Development
7
+ ---
8
+
9
+ # abaplint Rule Guidelines
10
+
11
+ **Searchable keywords**: abaplint, prefer_inline, inline declaration, char literal, string truncation,
12
+ no_inline_in_optional_branches, fully_type_constants, linting, static analysis,
13
+ run abaplint locally, check changed file, abapgit-agent lint,
14
+ keyword_case, sequential_blank, double_space, use_new, local_variable_names
15
+
16
+ This file covers rules that have **non-obvious or dangerous implications** — cases where applying
17
+ a rule mechanically (or accepting its quickfix) can introduce subtle bugs.
18
+
19
+ For project-specific rule guidance, add a `guidelines/abaplint-local.md` file to the project
20
+ repository. The `ref` command searches both bundled and project guidelines automatically.
21
+
22
+ ---
23
+
24
+ ## prefer_inline — Inline Declarations
25
+
26
+ ### What the rule does
27
+
28
+ Flags up-front `DATA` declarations and suggests replacing them with inline `DATA(var) = expr`:
29
+
30
+ ```abap
31
+ * Bad (flagged by rule)
32
+ DATA lv_count TYPE i.
33
+ lv_count = lines( lt_table ).
34
+
35
+ * Good (preferred by rule)
36
+ DATA(lv_count) = lines( lt_table ).
37
+ ```
38
+
39
+ This is safe when the RHS expression is a **function call, method call, or constructor
40
+ operator** — because the return type is fully defined.
41
+
42
+ ### The char-literal trap — NEVER apply the quickfix here
43
+
44
+ ```
45
+ ❌ DANGEROUS: DATA(var) = 'literal'.
46
+ ```
47
+
48
+ When the RHS is a quoted string literal, ABAP infers type `C LENGTH N` where N equals
49
+ the exact character count of the literal. Any subsequent `&&` concatenation computes the
50
+ correct longer string but **silently truncates it back to N characters**. The variable
51
+ never grows beyond the length of its initial value.
52
+
53
+ **This is the most dangerous quickfix the rule offers — it changes the runtime type.**
54
+
55
+ ```abap
56
+ * WRONG — produced by prefer_inline quickfix, causes silent truncation
57
+ DATA(lv_response) = '{"success":"X",'. " → TYPE C LENGTH 16
58
+ lv_response = lv_response && '"key":"val"'. " computed correctly, truncated to 16 chars
59
+ " lv_response is STILL '{"success":"X",' — the && had no effect
60
+
61
+ * CORRECT — use a string template to build the full value in one step
62
+ DATA(lv_key) = condense( val = CONV string( li_repo->get_key( ) ) ).
63
+ rv_result = |\{"success":"X","key":"{ lv_key }"\}|.
64
+
65
+ * ALSO CORRECT — explicit TYPE string, safe to concatenate
66
+ DATA lv_response TYPE string.
67
+ lv_response = '{"success":"X",'.
68
+ lv_response = lv_response && '"key":"val"}'.
69
+ ```
70
+
71
+ ### Safe vs unsafe patterns
72
+
73
+ | Pattern | Safe? | Why |
74
+ |---|---|---|
75
+ | `DATA(n) = lines( lt_tab ).` | ✅ | Return type `I` — no truncation risk |
76
+ | `DATA(lo) = NEW zcl_foo( ).` | ✅ | Object reference — fully typed |
77
+ | `DATA(ls) = CORRESPONDING #( ls_src ).` | ✅ | Inherits structure type |
78
+ | `DATA(lv) = lv_other.` | ✅ | Inherits type from source variable |
79
+ | `SELECT ... INTO TABLE @DATA(lt).` | ✅ | Type from DB dictionary |
80
+ | `DATA(lv) = 'literal'.` followed by `&&` | ❌ | Infers `C LENGTH N`, truncates |
81
+ | `DATA(lv) = 'literal'.` used only in `\|{ lv }\|` | ⚠️ | Technically works but misleading — prefer explicit type |
82
+ | `DATA(lv) = 'X'.` used as abap_bool flag | ✅ | `C LENGTH 1` is correct for flags |
83
+
84
+ ### Rule of thumb
85
+
86
+ > If the inline-declared variable will ever appear on the left side of `&&`,
87
+ > or be passed to a parameter typed `TYPE string`, declare it explicitly:
88
+ > `DATA lv_foo TYPE string.`
89
+
90
+ ---
91
+
92
+ ## no_inline_in_optional_branches
93
+
94
+ ### What the rule does
95
+
96
+ Flags inline `DATA(var)` declarations inside `IF`, `CASE/WHEN`, `LOOP`, `WHILE`, `DO`,
97
+ and `SELECT` loops — branches that may not execute, leaving the variable uninitialized
98
+ when code after the branch reads it.
99
+
100
+ ```abap
101
+ * Bad (flagged)
102
+ LOOP AT lt_items INTO DATA(ls_item).
103
+ DATA(lv_key) = ls_item-key. " declared inside LOOP — only set when loop runs
104
+ ENDLOOP.
105
+ WRITE lv_key. " undefined if lt_items was empty
106
+
107
+ * Good
108
+ DATA lv_key TYPE string.
109
+ LOOP AT lt_items INTO DATA(ls_item).
110
+ lv_key = ls_item-key.
111
+ ENDLOOP.
112
+ WRITE lv_key.
113
+ ```
114
+
115
+ **Exception**: `TRY/CATCH/CLEANUP` is explicitly NOT considered an optional branch by
116
+ the rule — inline declarations inside `TRY` are allowed.
117
+
118
+ ### When you see this rule triggered
119
+
120
+ Move the `DATA(var)` declaration out of the branch to the top of the method, giving it
121
+ an explicit type:
122
+
123
+ ```abap
124
+ * Before (flagged)
125
+ IF condition.
126
+ DATA(lv_result) = compute( ).
127
+ ENDIF.
128
+
129
+ * After (clean)
130
+ DATA lv_result TYPE string.
131
+ IF condition.
132
+ lv_result = compute( ).
133
+ ENDIF.
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Project-Specific Rule Overrides
139
+
140
+ Each project can add its own abaplint guidance by creating a file in the `guidelines/`
141
+ folder of the project repository:
142
+
143
+ ```
144
+ guidelines/
145
+ abaplint-local.md ← project-specific rule notes
146
+ ```
147
+
148
+ After creating the file, export it so the `ref` command can find it:
149
+
150
+ ```bash
151
+ abapgit-agent ref export
152
+ ```
153
+
154
+ Then `abapgit-agent ref "prefer_inline"` will surface both this bundled guidance
155
+ and the project-specific notes together.
156
+
157
+ **Example `guidelines/abaplint-local.md`:**
158
+
159
+ ```markdown
160
+ ## prefer_inline — project rules
161
+
162
+ This project's .abaplint.json enables prefer_inline.
163
+
164
+ Additional constraint: never inline-declare response-building variables.
165
+ All JSON response strings must use string templates (| ... |) directly
166
+ on rv_result — no intermediate lv_response variable at all.
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Running abaplint Locally Against Changed Files
172
+
173
+ Run this before pushing to catch issues early, matching what CI does.
174
+
175
+ ```bash
176
+ abapgit-agent lint
177
+ ```
178
+
179
+ This automatically detects changed `.abap` files (via `git diff`), creates a scoped
180
+ abaplint config for just those files, runs the check, and cleans up.
181
+
182
+ ### Options
183
+
184
+ ```bash
185
+ # Diff against a specific base branch (useful on a feature branch)
186
+ abapgit-agent lint --base main
187
+
188
+ # Check specific files explicitly
189
+ abapgit-agent lint --files src/zcl_foo.clas.abap,src/zcl_foo.clas.testclasses.abap
190
+
191
+ # Use a different abaplint config (default: .abaplint.json)
192
+ abapgit-agent lint --config .abaplint.json
193
+ ```
194
+
195
+ Run repeatedly after each fix until you see:
196
+
197
+ ```
198
+ abaplint: 0 issue(s) found, 1 file(s) analyzed
199
+ ```
200
+
201
+ ---
202
+
203
+ ### Common Issues and Fixes
204
+
205
+ | Rule | Error message | Fix |
206
+ |------|--------------|-----|
207
+ | `keyword_case` | `Keyword should be upper case: "class"` | Uppercase the keyword: `CLASS` |
208
+ | `sequential_blank` | `Remove sequential blank lines` | Max 1 blank line between blocks |
209
+ | `local_variable_names` | `<fs_data> does not match pattern` | Use `l`-prefixed name: `<ls_data>` |
210
+ | `double_space` | `Remove double space` | Single space around `=` in parameters |
211
+ | `use_new` | `Use NEW #() to instantiate` | Replace `CREATE OBJECT mo_foo` → `mo_foo = NEW #( )` |
212
+ | `method_parameter_names` | `Parameter name does not match pattern` | Use `iv_`, `it_`, `is_`, `io_` etc. prefixes |
213
+
214
+ See **abaplint-local.md** for the full naming convention prefix reference.
215
+
216
+ ---
217
+
218
+
219
+ ## See Also
220
+
221
+ - **common-errors.md** — char-literal truncation listed as a known error pattern
222
+ - **json.md** — safe patterns for building JSON strings in ABAP
223
+ - **workflow-detailed.md** — where abaplint fits in the development workflow
224
+ - **abaplint-local.md** — naming convention reference (prefixes for variables, parameters, field-symbols)
@@ -21,6 +21,7 @@ edit src/zcl_auth_handler.clas.abap
21
21
 
22
22
  # 3. Check syntax (CLAS/INTF/PROG/DDLS only, if independent)
23
23
  abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
24
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
24
25
 
25
26
  # 4. Commit
26
27
  git add src/zcl_auth_handler.clas.abap
@@ -112,6 +113,7 @@ git checkout main && git pull origin main
112
113
  git checkout -b feature/user-authentication
113
114
  edit src/zcl_auth_handler.clas.abap
114
115
  abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
116
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
115
117
  git add . && git commit -m "wip: add basic auth logic"
116
118
  git push origin feature/user-authentication
117
119
  git fetch origin main && git rebase origin/main
@@ -123,6 +125,8 @@ git fetch origin main && git rebase origin/main
123
125
  # If conflicts: resolve, git add, git rebase --continue
124
126
  git push origin feature/user-authentication --force-with-lease
125
127
  edit src/zcl_auth_handler.clas.abap
128
+ abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
129
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
126
130
  git add . && git commit -m "feat: complete auth logic"
127
131
  git push origin feature/user-authentication
128
132
  git fetch origin main && git rebase origin/main
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  layout: default
3
3
  title: CDS Testing
4
- nav_order: 17
4
+ nav_order: 19
5
5
  parent: ABAP Coding Guidelines
6
6
  grand_parent: ABAP Development
7
7
  ---
@@ -89,7 +89,38 @@ Find data elements: `abapgit-agent view --objects <TABLE> --type TABL`
89
89
 
90
90
  ---
91
91
 
92
+ ## Inline Declaration from String Literal — Silent Truncation
93
+
94
+ **Symptom**: A `&&` concatenation has no effect; the variable retains its initial value.
95
+
96
+ **Root cause**: `DATA(var) = 'literal'` infers type `C LENGTH N` (N = literal length).
97
+ Any subsequent `&&` computes the correct longer string but truncates it back to N chars.
98
+ The abaplint `prefer_inline` quickfix can introduce this bug automatically.
99
+
100
+ ```abap
101
+ * WRONG — lv_response stays '{"success":"X",' after && (16 chars, always truncated)
102
+ DATA(lv_response) = '{"success":"X",'.
103
+ lv_response = lv_response && '"key":"value"}'. " no effect!
104
+
105
+ * CORRECT — use string template
106
+ DATA(lv_key) = condense( val = CONV string( li_repo->get_key( ) ) ).
107
+ rv_result = |\{"success":"X","key":"{ lv_key }"\}|.
108
+
109
+ * ALSO CORRECT — explicit TYPE string
110
+ DATA lv_response TYPE string.
111
+ lv_response = '{"success":"X",'.
112
+ lv_response = lv_response && '"key":"value"}'. " works correctly
113
+ ```
114
+
115
+ **Fix**: Replace the inline declaration + `&&` chain with a string template,
116
+ or declare the variable explicitly with `TYPE string`.
117
+
118
+ → See `abaplint.md` for full guidance on the `prefer_inline` rule.
119
+
120
+ ---
121
+
92
122
  ## See Also
93
123
  - **ABAP SQL** (sql.md) - for SQL syntax rules
94
124
  - **CDS Views** (cds.md) - for CDS selection patterns
95
125
  - **abapGit** (abapgit.md) - for XML metadata templates
126
+ - **abaplint** (abaplint.md) - for abaplint rule guidance and known quickfix traps
@@ -30,6 +30,7 @@ This folder contains detailed ABAP coding guidelines that can be searched using
30
30
  | `workflow-detailed.md` | Development Workflow (Detailed) |
31
31
  | `object-creation.md` | Object Creation (XML metadata, local classes) |
32
32
  | `cds-testing.md` | CDS Testing (Test Double Framework) |
33
+ | `abaplint.md` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
33
34
 
34
35
  ## Usage
35
36
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  layout: default
3
3
  title: Probe and PoC Guide
4
- nav_order: 19
4
+ nav_order: 21
5
5
  parent: ABAP Coding Guidelines
6
6
  grand_parent: ABAP Development
7
7
  ---
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  layout: default
3
3
  title: run Command Guide
4
- nav_order: 18
4
+ nav_order: 20
5
5
  parent: ABAP Coding Guidelines
6
6
  grand_parent: ABAP Development
7
7
  ---
@@ -257,31 +257,11 @@ ENDMETHOD.
257
257
 
258
258
  ## Test Double Patterns
259
259
 
260
- ### Manual Test Double (Local Class)
260
+ **Prefer the ABAP Test Double Framework** (`cl_abap_testdouble`) over manual doubles.
261
+ Use manual doubles only when stateful logic is required (e.g. call-count tracking, results that
262
+ vary per call, or complex setup that configure_call cannot express).
261
263
 
262
- ```abap
263
- " Create test double class
264
- CLASS ltd_mock_reader DEFINITION FOR TESTING.
265
- PUBLIC SECTION.
266
- INTERFACES zif_data_reader PARTIALLY IMPLEMENTED.
267
- METHODS set_result_data
268
- IMPORTING it_data TYPE ANY TABLE.
269
- PRIVATE SECTION.
270
- DATA mt_data TYPE ANY TABLE.
271
- ENDCLASS.
272
-
273
- CLASS ltd_mock_reader IMPLEMENTATION.
274
- METHOD set_result_data.
275
- mt_data = it_data.
276
- ENDMETHOD.
277
-
278
- METHOD zif_data_reader~read_all.
279
- rt_data = mt_data.
280
- ENDMETHOD.
281
- ENDCLASS.
282
- ```
283
-
284
- ### Using ABAP Test Double Framework
264
+ ### Using ABAP Test Double Framework (Recommended)
285
265
 
286
266
  ```abap
287
267
  " Step 1: Declare with correct interface type, then assign
@@ -315,6 +295,30 @@ lo_mock->my_method( ... ).
315
295
  - Use `returning(value = ...)` not `IMPORTING`
316
296
  - Call method after configure_call to register the configuration
317
297
 
298
+ ### Manual Test Double (Local Class — use only when stateful logic is needed)
299
+
300
+ ```abap
301
+ " Create test double class
302
+ CLASS ltd_mock_reader DEFINITION FOR TESTING.
303
+ PUBLIC SECTION.
304
+ INTERFACES zif_data_reader PARTIALLY IMPLEMENTED.
305
+ METHODS set_result_data
306
+ IMPORTING it_data TYPE ANY TABLE.
307
+ PRIVATE SECTION.
308
+ DATA mt_data TYPE ANY TABLE.
309
+ ENDCLASS.
310
+
311
+ CLASS ltd_mock_reader IMPLEMENTATION.
312
+ METHOD set_result_data.
313
+ mt_data = it_data.
314
+ ENDMETHOD.
315
+
316
+ METHOD zif_data_reader~read_all.
317
+ rt_data = mt_data.
318
+ ENDMETHOD.
319
+ ENDCLASS.
320
+ ```
321
+
318
322
  ### Mocking EXPORT Parameters
319
323
 
320
324
  Some methods use EXPORT parameters instead of returning values. Use `set_parameter`:
@@ -24,9 +24,22 @@ grand_parent: ABAP Development
24
24
  │ │
25
25
  │ ├─► Errors? → Fix locally (no commit needed), re-run syntax
26
26
  │ │
27
+ │ └─► Clean ✅ → Proceed to 4b
28
+
29
+ └─► Other types (FUGR, TABL, etc.) → Skip syntax, go to 4b
30
+
31
+
32
+ 4b. abaplint (OPTIONAL — only if .abaplint.json exists in repo root)
33
+
34
+ ├─► .abaplint.json exists → npx @abaplint/cli .abaplint.json
35
+ │ │
36
+ │ ├─► Issues? → Fix locally, re-run abaplint
37
+ │ │ ⚠️ Before applying any quickfix: run abapgit-agent ref --topic abaplint
38
+ │ │ Quickfixes for prefer_inline can introduce silent type truncation bugs.
39
+ │ │
27
40
  │ └─► Clean ✅ → Proceed to commit
28
41
 
29
- └─► Other types (FUGR, TABL, etc.) → Skip syntax, go to commit
42
+ └─► No .abaplint.json → Skip, go to commit
30
43
 
31
44
 
32
45
  5. Commit and push → git add . && git commit && git push
@@ -140,8 +153,9 @@ git push
140
153
  abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
141
154
  ```
142
155
 
143
- **When to use syntax vs inspect vs view**:
144
- - **syntax**: Check LOCAL code BEFORE commit (CLAS, INTF, PROG, DDLS)
156
+ **When to use syntax vs abaplint vs inspect vs view**:
157
+ - **syntax**: Check LOCAL ABAP syntax BEFORE commit (CLAS, INTF, PROG, DDLS)
158
+ - **abaplint**: Check LOCAL code style/quality BEFORE commit (only if .abaplint.json present)
145
159
  - **inspect**: Check ACTIVATED code AFTER pull (all types, runs Code Inspector)
146
160
  - **view**: Understand object STRUCTURE (not for debugging errors)
147
161
 
@@ -164,22 +178,25 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
164
178
  └─ Unrelated bug fixes across files? → INDEPENDENT
165
179
 
166
180
  3. For SUPPORTED types (CLAS/INTF/PROG/DDLS):
167
- ├─ INDEPENDENT files → Run syntax → Fix errors → Commit → Push → Pull
181
+ ├─ INDEPENDENT files → Run syntax → [abaplint if enabled] → Fix errors → Commit → Push → Pull
168
182
 
169
183
  └─ DEPENDENT files (NEW objects):
170
184
  ├─ RECOMMENDED: Create underlying object first (interface, base class, etc.)
171
- │ 1. Create underlying object → Syntax → Commit → Push → Pull
172
- │ 2. Create dependent object → Syntax (works!) → Commit → Push → Pull
185
+ │ 1. Create underlying object → Syntax → [abaplint] → Commit → Push → Pull
186
+ │ 2. Create dependent object → Syntax (works!) → [abaplint] → Commit → Push → Pull
173
187
  │ ✅ Benefits: Both syntax checks work, cleaner workflow
174
188
 
175
189
  └─ ALTERNATIVE: If interface design uncertain, commit both together
176
- → Skip syntax → Commit both → Push → Pull → (if errors: inspect)
190
+ → Skip syntax → [abaplint] → Commit both → Push → Pull → (if errors: inspect)
177
191
 
178
192
  4. For UNSUPPORTED types (FUGR, TABL, etc.):
179
- Write code → Skip syntax → Commit → Push → Pull → (if errors: inspect)
193
+ Write code → Skip syntax → [abaplint] → Commit → Push → Pull → (if errors: inspect)
180
194
 
181
195
  5. For MIXED types (some supported + some unsupported):
182
- Write all code → Run syntax on independent supported files ONLY → Commit ALL → Push → Pull ALL
196
+ Write all code → Run syntax on independent supported files ONLY → [abaplint] → Commit ALL → Push → Pull ALL
197
+
198
+ [abaplint] = run npx @abaplint/cli .abaplint.json only if .abaplint.json exists in repo root
199
+ before applying any quickfix: run abapgit-agent ref --topic abaplint
183
200
  ```
184
201
 
185
202
  **Example workflows:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.13.6",
3
+ "version": "1.14.0",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -29,5 +29,6 @@ module.exports = {
29
29
  init: require('./init'),
30
30
  pull: require('./pull'),
31
31
  upgrade: require('./upgrade'),
32
- transport: require('./transport')
32
+ transport: require('./transport'),
33
+ lint: require('./lint')
33
34
  };
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * lint command - Run abaplint on changed ABAP files
5
+ *
6
+ * Detects files changed relative to a base branch (or HEAD~1),
7
+ * creates a scoped abaplint config for just those files, and runs the check.
8
+ *
9
+ * Usage:
10
+ * abapgit-agent lint
11
+ * abapgit-agent lint --config .abaplint.json
12
+ * abapgit-agent lint --base main
13
+ * abapgit-agent lint --files src/foo.clas.abap,src/foo.clas.testclasses.abap
14
+ * abapgit-agent lint --outformat checkstyle --outfile reports/abaplint-results.xml
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execSync, spawnSync } = require('child_process');
20
+
21
+ module.exports = {
22
+ name: 'lint',
23
+ description: 'Run abaplint on changed ABAP files',
24
+ requiresAbapConfig: false,
25
+
26
+ execute(args) {
27
+ const configPath = argValue(args, '--config') || '.abaplint.json';
28
+ const baseBranch = argValue(args, '--base');
29
+ const filesArg = argValue(args, '--files');
30
+ const outformat = argValue(args, '--outformat');
31
+ const outfile = argValue(args, '--outfile');
32
+
33
+ // ── Resolve changed files ─────────────────────────────────────────────────
34
+ let abapFiles;
35
+ if (filesArg) {
36
+ abapFiles = filesArg.split(',').map(f => f.trim()).filter(f => f.endsWith('.abap'));
37
+ } else {
38
+ abapFiles = detectChangedAbapFiles(baseBranch);
39
+ }
40
+
41
+ if (abapFiles.length === 0) {
42
+ console.log('No changed .abap files found — nothing to lint.');
43
+ return;
44
+ }
45
+
46
+ if (!outfile) {
47
+ console.log(`\nLinting ${abapFiles.length} file(s):`);
48
+ abapFiles.forEach(f => console.log(` ${f}`));
49
+ console.log('');
50
+ }
51
+
52
+ // ── Load and scope the abaplint config ────────────────────────────────────
53
+ if (!fs.existsSync(configPath)) {
54
+ console.error(`Error: abaplint config not found: ${configPath}`);
55
+ console.error('Run from the project root, or pass --config <path>.');
56
+ process.exit(1);
57
+ }
58
+
59
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
60
+ cfg.global.files = abapFiles.map(f => `/${f}`);
61
+
62
+ const scopedConfig = '.abaplint-local.json';
63
+ fs.writeFileSync(scopedConfig, JSON.stringify(cfg, null, 2));
64
+
65
+ // ── Run abaplint ──────────────────────────────────────────────────────────
66
+ try {
67
+ const formatArgs = outformat ? `--outformat ${outformat}` : '';
68
+ const fileArgs = outfile ? `--outfile ${outfile}` : '';
69
+ const result = spawnSync(
70
+ `npx @abaplint/cli@latest ${scopedConfig} ${formatArgs} ${fileArgs}`,
71
+ { stdio: 'inherit', shell: true }
72
+ );
73
+ if (result.status !== 0) {
74
+ process.exitCode = result.status;
75
+ }
76
+ } finally {
77
+ fs.unlinkSync(scopedConfig);
78
+ }
79
+ }
80
+ };
81
+
82
+ // ── Helpers ───────────────────────────────────────────────────────────────────
83
+
84
+ function argValue(args, flag) {
85
+ const idx = args.indexOf(flag);
86
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
87
+ }
88
+
89
+ /**
90
+ * Detect changed .abap files using git diff.
91
+ * - If on a PR branch (CHANGE_TARGET set, e.g. in CI): diffs against that target.
92
+ * - If --base is given: diffs against that branch.
93
+ * - Otherwise: diffs HEAD~1..HEAD (last commit).
94
+ */
95
+ function detectChangedAbapFiles(baseBranch) {
96
+ const base = baseBranch
97
+ || (process.env.CHANGE_TARGET ? `origin/${process.env.CHANGE_TARGET}` : null);
98
+
99
+ let diffCmd;
100
+ if (base) {
101
+ diffCmd = `git diff --name-only ${base}...HEAD -- '*.abap'`;
102
+ } else {
103
+ // Fall back to uncommitted changes first, then last commit
104
+ const uncommitted = runGit('git diff --name-only HEAD -- *.abap').filter(Boolean);
105
+ if (uncommitted.length > 0) return filterAbapFiles(uncommitted);
106
+ diffCmd = `git diff --name-only HEAD~1 HEAD -- '*.abap'`;
107
+ }
108
+
109
+ return filterAbapFiles(runGit(diffCmd));
110
+ }
111
+
112
+ function runGit(cmd) {
113
+ try {
114
+ return execSync(cmd, { encoding: 'utf8' }).trim().split('\n').filter(Boolean);
115
+ } catch {
116
+ return [];
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Keep only files that look like ABAP source files
122
+ * (name.type.abap or name.type.subtype.abap).
123
+ */
124
+ function filterAbapFiles(files) {
125
+ return files.filter(f => {
126
+ const parts = path.basename(f).split('.');
127
+ return (parts.length === 3 || parts.length === 4) &&
128
+ parts[parts.length - 1].toLowerCase() === 'abap';
129
+ });
130
+ }
@@ -40,7 +40,18 @@ function getBranch() {
40
40
 
41
41
  const content = fs.readFileSync(headPath, 'utf8').trim();
42
42
  const match = content.match(/ref: refs\/heads\/(.+)/);
43
- return match ? match[1] : 'main';
43
+ if (match) return match[1];
44
+
45
+ // Detached HEAD (e.g. Jenkins checkout) — use Jenkins env vars:
46
+ // CHANGE_BRANCH is set in PR builds (actual head branch name),
47
+ // BRANCH_NAME is set in all builds (branch name or "PR-N" for PRs).
48
+ // For PR builds BRANCH_NAME is "PR-N" so prefer CHANGE_BRANCH first.
49
+ if (process.env.CHANGE_BRANCH) return process.env.CHANGE_BRANCH;
50
+ if (process.env.BRANCH_NAME && !process.env.BRANCH_NAME.startsWith('PR-')) {
51
+ return process.env.BRANCH_NAME;
52
+ }
53
+
54
+ return 'main';
44
55
  }
45
56
 
46
57
  /**