abapgit-agent 1.6.1 → 1.7.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/guidelines/02_exceptions.md +24 -0
- package/abap/guidelines/03_testing.md +19 -0
- package/abap/guidelines/05_classes.md +111 -0
- package/abap/guidelines/06_objects.md +45 -92
- package/abap/guidelines/08_abapgit.md +11 -0
- package/abap/guidelines/09_unit_testable_code.md +21 -0
- package/bin/abapgit-agent +108 -2
- package/package.json +5 -1
- package/src/abap-client.js +46 -0
- package/src/agent.js +48 -0
|
@@ -174,3 +174,27 @@ ENDTRY.
|
|
|
174
174
|
| Multiple callers need to handle exception | Add RAISING to method definition |
|
|
175
175
|
| Exception should be handled internally | Use TRY-CATCH in implementation |
|
|
176
176
|
| Interface method | Add RAISING to interface, or use TRY-CATCH in class |
|
|
177
|
+
|
|
178
|
+
### RAISING Clause in Method Calls
|
|
179
|
+
|
|
180
|
+
The `RAISING` clause **cannot be used in method call statements**. It can only be used in method definitions.
|
|
181
|
+
|
|
182
|
+
```abap
|
|
183
|
+
" WRONG - syntax error
|
|
184
|
+
lo_handler->read( ... ) RAISING cx_dd_ddl_check.
|
|
185
|
+
|
|
186
|
+
" CORRECT - use TRY-CATCH
|
|
187
|
+
TRY.
|
|
188
|
+
lo_handler->read( ... ).
|
|
189
|
+
CATCH cx_dd_ddl_check.
|
|
190
|
+
" Handle error
|
|
191
|
+
ENDTRY.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Correct usage in method definitions:**
|
|
195
|
+
```abap
|
|
196
|
+
" Interface/Class method definition
|
|
197
|
+
METHODS read
|
|
198
|
+
IMPORTING iv_name TYPE string
|
|
199
|
+
RAISING cx_static_check.
|
|
200
|
+
``` |
|
|
@@ -79,6 +79,25 @@ Examples of compliant names:
|
|
|
79
79
|
- `test_exec_files` (16 chars)
|
|
80
80
|
- `test_interface` (15 chars)
|
|
81
81
|
|
|
82
|
+
### Test Methods and RAISING Clause
|
|
83
|
+
|
|
84
|
+
If a test method calls methods that raise exceptions, add `RAISING` to the method definition:
|
|
85
|
+
|
|
86
|
+
```abap
|
|
87
|
+
" CORRECT - declare that method can raise exceptions
|
|
88
|
+
METHODS test_validate_ddls FOR TESTING RAISING cx_static_check.
|
|
89
|
+
METHODS test_read_data FOR TESTING RAISING cx_dd_ddl_check.
|
|
90
|
+
|
|
91
|
+
" Then implement with TRY-CATCH if needed
|
|
92
|
+
METHOD test_validate_ddls.
|
|
93
|
+
TRY.
|
|
94
|
+
mo_cut->some_method( ).
|
|
95
|
+
CATCH cx_static_check.
|
|
96
|
+
" Handle exception
|
|
97
|
+
ENDTRY.
|
|
98
|
+
ENDMETHOD.
|
|
99
|
+
```
|
|
100
|
+
|
|
82
101
|
### Common Assertions
|
|
83
102
|
|
|
84
103
|
```abap
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
2. Constructor - line 20
|
|
8
8
|
3. Interfaces - line 35
|
|
9
9
|
4. Inline Declaration - line 50
|
|
10
|
+
5. Abstract Methods - line 99
|
|
11
|
+
6. FINAL Class Limitation - line 117
|
|
12
|
+
7. Working with TYPE any - line 135
|
|
10
13
|
|
|
11
14
|
## ABAP Class Definition - Must Use PUBLIC
|
|
12
15
|
|
|
@@ -56,3 +59,111 @@ ENDCLASS.
|
|
|
56
59
|
|
|
57
60
|
**Wrong**: `METHOD do_something.` - parameter `iv_param` will be unknown
|
|
58
61
|
**Correct**: `METHOD zif_my_interface~do_something.` - parameters recognized
|
|
62
|
+
|
|
63
|
+
## Use Interface Type for References
|
|
64
|
+
|
|
65
|
+
When a class implements an interface, use the **interface type** instead of the class type for references:
|
|
66
|
+
|
|
67
|
+
```abap
|
|
68
|
+
" Interface definition
|
|
69
|
+
INTERFACE zif_my_interface PUBLIC.
|
|
70
|
+
METHODS do_something RETURNING VALUE(rv_result) TYPE string.
|
|
71
|
+
ENDINTERFACE.
|
|
72
|
+
|
|
73
|
+
" Class implements interface
|
|
74
|
+
CLASS zcl_my_class DEFINITION PUBLIC.
|
|
75
|
+
PUBLIC SECTION.
|
|
76
|
+
INTERFACES zif_my_interface.
|
|
77
|
+
CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zif_my_interface.
|
|
78
|
+
ENDCLASS.
|
|
79
|
+
|
|
80
|
+
" Caller - use interface type, not class type
|
|
81
|
+
CLASS zcl_consumer DEFINITION PUBLIC.
|
|
82
|
+
PRIVATE SECTION.
|
|
83
|
+
DATA mo_instance TYPE REF TO zif_my_interface. " <- Use interface type
|
|
84
|
+
ENDCLASS.
|
|
85
|
+
|
|
86
|
+
METHOD zcl_consumer->do_something.
|
|
87
|
+
mo_instance = zcl_my_class=>get_instance( ).
|
|
88
|
+
|
|
89
|
+
" Call without interface prefix - cleaner code
|
|
90
|
+
DATA(lv_result) = mo_instance->do_something( ).
|
|
91
|
+
ENDMETHOD.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Benefits:**
|
|
95
|
+
- Cleaner code: `mo_instance->method( )` instead of `mo_instance->zif_my_interface~method( )`
|
|
96
|
+
- Flexibility: Can swap implementation class without changing caller (dependency inversion)
|
|
97
|
+
- Consistency: All callers use the same interface type
|
|
98
|
+
|
|
99
|
+
**Key rule:** Always use `REF TO zif_xxx` not `REF TO zcl_xxx` for instance variables and parameters.
|
|
100
|
+
|
|
101
|
+
## Abstract Methods
|
|
102
|
+
|
|
103
|
+
The ABSTRACT keyword must come immediately after the method name:
|
|
104
|
+
|
|
105
|
+
```abap
|
|
106
|
+
" ✅ Correct - ABSTRACT right after method name
|
|
107
|
+
METHODS get_name ABSTRACT
|
|
108
|
+
RETURNING VALUE(rv_name) TYPE string.
|
|
109
|
+
|
|
110
|
+
" ❌ Wrong - ABSTRACT after parameters (syntax error)
|
|
111
|
+
METHODS get_name
|
|
112
|
+
RETURNING VALUE(rv_name) TYPE string
|
|
113
|
+
ABSTRACT.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## FINAL Class Limitation
|
|
117
|
+
|
|
118
|
+
A FINAL class cannot have abstract methods. Use plain REDEFINITION instead:
|
|
119
|
+
|
|
120
|
+
```abap
|
|
121
|
+
" ❌ Wrong in FINAL class - syntax error
|
|
122
|
+
CLASS zcl_my_class DEFINITION PUBLIC FINAL.
|
|
123
|
+
METHODS parse_request ABSTRACT REDEFINITION.
|
|
124
|
+
ENDCLASS.
|
|
125
|
+
|
|
126
|
+
" ✅ Correct in FINAL class - use REDEFINITION only
|
|
127
|
+
CLASS zcl_my_class DEFINITION PUBLIC FINAL.
|
|
128
|
+
METHODS parse_request REDEFINITION.
|
|
129
|
+
ENDCLASS.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Working with TYPE any
|
|
133
|
+
|
|
134
|
+
TYPE any cannot be used with CREATE DATA. When a base class defines parameters with TYPE any, use a typed local variable in the subclass:
|
|
135
|
+
|
|
136
|
+
```abap
|
|
137
|
+
" Base class defines:
|
|
138
|
+
CLASS zcl_base DEFINITION PUBLIC ABSTRACT.
|
|
139
|
+
PROTECTED SECTION.
|
|
140
|
+
METHODS parse_request
|
|
141
|
+
IMPORTING iv_json TYPE string
|
|
142
|
+
EXPORTING es_request TYPE any.
|
|
143
|
+
ENDCLASS.
|
|
144
|
+
|
|
145
|
+
" Subclass implementation:
|
|
146
|
+
CLASS zcl_subclass DEFINITION PUBLIC FINAL.
|
|
147
|
+
INHERITING FROM zcl_base.
|
|
148
|
+
PROTECTED SECTION.
|
|
149
|
+
METHODS parse_request REDEFINITION.
|
|
150
|
+
ENDCLASS.
|
|
151
|
+
|
|
152
|
+
CLASS zcl_subclass IMPLEMENTATION.
|
|
153
|
+
METHOD parse_request.
|
|
154
|
+
" Use typed local variable
|
|
155
|
+
DATA: ls_request TYPE ty_my_params.
|
|
156
|
+
|
|
157
|
+
/ui2/cl_json=>deserialize(
|
|
158
|
+
EXPORTING json = iv_json
|
|
159
|
+
CHANGING data = ls_request ).
|
|
160
|
+
|
|
161
|
+
es_request = ls_request. " Assign typed to generic
|
|
162
|
+
ENDMETHOD.
|
|
163
|
+
ENDCLASS.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Key points:**
|
|
167
|
+
- Declare a local variable with the concrete type
|
|
168
|
+
- Deserialize JSON into the typed local variable
|
|
169
|
+
- Assign to the generic TYPE any parameter
|
|
@@ -1,110 +1,63 @@
|
|
|
1
|
-
# ABAP
|
|
1
|
+
# ABAP Object Naming Conventions
|
|
2
2
|
|
|
3
|
-
**Searchable keywords**: naming convention, Z prefix, namespace, object type, CLAS, INTF, PROG, TABL, DDLS
|
|
3
|
+
**Searchable keywords**: naming convention, Z prefix, namespace, object type, CLAS, INTF, PROG, TABL, DDLS
|
|
4
4
|
|
|
5
5
|
## TOPICS IN THIS FILE
|
|
6
|
-
1.
|
|
7
|
-
2.
|
|
8
|
-
3.
|
|
6
|
+
1. Naming Conventions
|
|
7
|
+
2. ABAP Object Types
|
|
8
|
+
3. XML Metadata (see guidelines/08_abapgit.md)
|
|
9
9
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
**CRITICAL CHECKLIST - Never Forget!**
|
|
13
|
-
|
|
14
|
-
When creating ANY new ABAP object file, you MUST also create its XML metadata file:
|
|
10
|
+
## Naming Conventions
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
12
|
+
Use `Z_` or `Y_` prefix for custom objects:
|
|
13
|
+
|
|
14
|
+
| Object Type | Prefix | Example |
|
|
15
|
+
|-------------|--------|---------|
|
|
16
|
+
| Class | ZCL_ | ZCL_MY_CLASS |
|
|
17
|
+
| Interface | ZIF_ | ZIF_MY_INTERFACE |
|
|
18
|
+
| Program | Z | ZMY_PROGRAM |
|
|
19
|
+
| Package | $ | $MY_PACKAGE |
|
|
20
|
+
| Table | Z | ZMY_TABLE |
|
|
21
|
+
| CDS View | ZC | ZC_MY_VIEW |
|
|
22
|
+
| CDS Entity | ZE | ZE_MY_ENTITY |
|
|
23
|
+
| Data Element | Z | ZMY_ELEMENT |
|
|
24
|
+
| Structure | Z | ZMY_STRUCTURE |
|
|
25
|
+
| Table Type | Z | ZMY_TABLE_TYPE |
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
## ABAP Object Types
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
Common object types in this project:
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
| Type | Description | File Suffix |
|
|
32
|
+
|------|-------------|-------------|
|
|
33
|
+
| CLAS | Classes | .clas.abap |
|
|
34
|
+
| PROG | Programs | .prog.abap |
|
|
35
|
+
| FUGR | Function Groups | .fugr.abap |
|
|
36
|
+
| INTF | Interfaces | .intf.abap |
|
|
37
|
+
| TABL | Tables | .tabl.abap |
|
|
38
|
+
| STRU | Structures | .stru.abap |
|
|
39
|
+
| DTEL | Data Elements | .dtel.abap |
|
|
40
|
+
| TTYP | Table Types | .ttyp.abap |
|
|
41
|
+
| DDLS | CDS Views | .ddls.asddls |
|
|
42
|
+
| DDLX | CDS Entities | .ddlx.asddlx |
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
29
|
-
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
30
|
-
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
31
|
-
<asx:values>
|
|
32
|
-
<VSEOCLASS>
|
|
33
|
-
<CLSNAME>ZCL_ABGAGT_UTIL</CLSNAME>
|
|
34
|
-
<LANGU>E</LANGU>
|
|
35
|
-
<DESCRIPT>Description</DESCRIPT>
|
|
36
|
-
<EXPOSURE>2</EXPOSURE>
|
|
37
|
-
<STATE>1</STATE>
|
|
38
|
-
<UNICODE>X</UNICODE>
|
|
39
|
-
</VSEOCLASS>
|
|
40
|
-
</asx:values>
|
|
41
|
-
</asx:abap>
|
|
42
|
-
</abapGit>
|
|
43
|
-
```
|
|
44
|
+
## XML Metadata
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
**See guidelines/08_abapgit.md for XML templates.**
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
Each ABAP object requires an XML metadata file for abapGit. Quick reference:
|
|
48
49
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<VSEOINTERF>
|
|
55
|
-
<CLSNAME>ZIF_ABGAGT_UTIL</CLSNAME>
|
|
56
|
-
<LANGU>E</LANGU>
|
|
57
|
-
<DESCRIPT>Description</DESCRIPT>
|
|
58
|
-
<EXPOSURE>2</EXPOSURE>
|
|
59
|
-
<STATE>1</STATE>
|
|
60
|
-
<UNICODE>X</UNICODE>
|
|
61
|
-
</VSEOINTERF>
|
|
62
|
-
</asx:values>
|
|
63
|
-
</asx:abap>
|
|
64
|
-
</abapGit>
|
|
50
|
+
```
|
|
51
|
+
Class: zcl_*.clas.xml
|
|
52
|
+
Interface: zif_*.intf.xml
|
|
53
|
+
Table: z*.tabl.xml
|
|
54
|
+
CDS View: zc_*.ddls.xml
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
1. **CRITICAL: Push to git BEFORE pulling into ABAP**
|
|
70
|
-
- Always commit and push ABAP files to git first
|
|
71
|
-
- Then run `abapgit-agent pull` to activate in ABAP
|
|
72
|
-
- Never run `abapgit-agent pull` without pushing changes first
|
|
73
|
-
|
|
74
|
-
2. **Only pull ABAP files** - The XML metadata stays in git:
|
|
75
|
-
```bash
|
|
76
|
-
abapgit-agent pull --files zcl_my_class.clas.abap
|
|
77
|
-
```
|
|
78
|
-
3. abapGit reads the XML from git to deserialize the ABAP code
|
|
79
|
-
4. XML files are NOT activated in ABAP - they are only for abapGit
|
|
80
|
-
|
|
57
|
+
**Important**: Always push to git BEFORE running pull:
|
|
81
58
|
```bash
|
|
82
|
-
# After making changes to ABAP files
|
|
83
59
|
git add .
|
|
84
|
-
git commit -m "
|
|
85
|
-
git push
|
|
86
|
-
|
|
87
|
-
# Then validate in ABAP system (single file - fast)
|
|
60
|
+
git commit -m "Changes"
|
|
61
|
+
git push # CRITICAL: Push FIRST
|
|
88
62
|
abapgit-agent pull --files abap/zcl_my_class.clas.abap
|
|
89
|
-
|
|
90
|
-
# Or validate all files
|
|
91
|
-
abapgit-agent pull
|
|
92
63
|
```
|
|
93
|
-
|
|
94
|
-
## Naming Conventions
|
|
95
|
-
|
|
96
|
-
- Use `Z_` or `Y_` prefix for custom objects
|
|
97
|
-
- Class names: `ZCL_<NAME>`
|
|
98
|
-
- Interface names: `ZIF_<NAME>`
|
|
99
|
-
- Programs: `Z<NAME>`
|
|
100
|
-
- Package: `$<PROJECT_NAME>`
|
|
101
|
-
|
|
102
|
-
## ABAP Object Types
|
|
103
|
-
|
|
104
|
-
Common object types in this project:
|
|
105
|
-
- `CLAS` - Classes
|
|
106
|
-
- `PROG` - Programs
|
|
107
|
-
- `FUGR` - Function Groups
|
|
108
|
-
- `INTF` - Interfaces
|
|
109
|
-
- `TABL` - Tables
|
|
110
|
-
- `DDLS` - Data Definitions
|
|
@@ -27,6 +27,7 @@ Key XML Settings:
|
|
|
27
27
|
Table DELIVERY: A=Application, C=Customizing
|
|
28
28
|
CDS SOURCE_TYPE: V=View, C=Consumption
|
|
29
29
|
Test Class XML: <WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
|
|
30
|
+
Local Classes: <CLSCCINCL>X</CLSCCINCL>
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
**Searchable keywords**: class xml, interface xml, table xml, cds xml, test class, exposure, serializer, abapgit
|
|
@@ -69,6 +70,16 @@ abapGit needs XML files to:
|
|
|
69
70
|
- `STATE`: State (1 = Active)
|
|
70
71
|
- `UNICODE`: Unicode encoding (X = Yes)
|
|
71
72
|
|
|
73
|
+
**Local Classes**: If the class has local classes (e.g., test doubles), add:
|
|
74
|
+
- `<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>` - for test classes
|
|
75
|
+
- `<CLSCCINCL>X</CLSCCINCL>` - for local class definitions
|
|
76
|
+
|
|
77
|
+
**Local Class Files**:
|
|
78
|
+
| File | Purpose |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| `zcl_xxx.clas.locals_def.abap` | Local class definitions |
|
|
81
|
+
| `zcl_xxx.clas.locals_imp.abap` | Local class implementations |
|
|
82
|
+
|
|
72
83
|
---
|
|
73
84
|
|
|
74
85
|
### Interface (INTF)
|
|
@@ -113,6 +113,27 @@ DATA mo_agent TYPE REF TO zcl_abgagt_agent. " Concrete class!
|
|
|
113
113
|
|
|
114
114
|
This allows you to replace the implementation with test doubles.
|
|
115
115
|
|
|
116
|
+
### Define Types in Interface
|
|
117
|
+
|
|
118
|
+
Define types needed by the interface directly in the interface to keep it self-contained:
|
|
119
|
+
|
|
120
|
+
```abap
|
|
121
|
+
INTERFACE zif_my_handler.
|
|
122
|
+
" Define types needed by the interface
|
|
123
|
+
TYPES: BEGIN OF ty_response,
|
|
124
|
+
success TYPE abap_bool,
|
|
125
|
+
message TYPE string,
|
|
126
|
+
END OF ty_response.
|
|
127
|
+
|
|
128
|
+
" Use the type in method signatures
|
|
129
|
+
METHODS process
|
|
130
|
+
IMPORTING iv_data TYPE string
|
|
131
|
+
RETURNING VALUE(rs_response) TYPE ty_response.
|
|
132
|
+
ENDINTERFACE.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This makes it easier for test doubles to implement the interface without needing separate type definitions.
|
|
136
|
+
|
|
116
137
|
### 3. Make Dependencies Injectable via Constructor
|
|
117
138
|
|
|
118
139
|
**Use constructor injection, not setter injection.**
|
package/bin/abapgit-agent
CHANGED
|
@@ -2067,9 +2067,9 @@ Examples:
|
|
|
2067
2067
|
console.log(` ${description}`);
|
|
2068
2068
|
}
|
|
2069
2069
|
|
|
2070
|
-
// Display source code for classes, interfaces, and
|
|
2070
|
+
// Display source code for classes, interfaces, CDS views, and programs/source includes
|
|
2071
2071
|
const source = obj.SOURCE || obj.source || '';
|
|
2072
|
-
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
|
|
2072
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program')) {
|
|
2073
2073
|
console.log('');
|
|
2074
2074
|
// Replace escaped newlines with actual newlines and display
|
|
2075
2075
|
const displaySource = source.replace(/\\n/g, '\n');
|
|
@@ -2439,6 +2439,105 @@ Examples:
|
|
|
2439
2439
|
break;
|
|
2440
2440
|
}
|
|
2441
2441
|
|
|
2442
|
+
case 'where': {
|
|
2443
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
2444
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
2445
|
+
console.error('Error: --objects parameter required');
|
|
2446
|
+
console.error('Usage: abapgit-agent where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]');
|
|
2447
|
+
console.error('Example: abapgit-agent where --objects ZCL_MY_CLASS');
|
|
2448
|
+
console.error('Example: abapgit-agent where --objects ZIF_MY_INTERFACE');
|
|
2449
|
+
console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20');
|
|
2450
|
+
process.exit(1);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
2454
|
+
const typeArg = args.indexOf('--type');
|
|
2455
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
2456
|
+
const limitArg = args.indexOf('--limit');
|
|
2457
|
+
const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 100;
|
|
2458
|
+
const jsonOutput = args.includes('--json');
|
|
2459
|
+
|
|
2460
|
+
console.log(`\n Where-used list for ${objects.length} object(s)`);
|
|
2461
|
+
|
|
2462
|
+
const config = loadConfig();
|
|
2463
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
2464
|
+
|
|
2465
|
+
const data = {
|
|
2466
|
+
objects: objects,
|
|
2467
|
+
limit: Math.min(Math.max(1, limit), 500)
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
if (type) {
|
|
2471
|
+
data.type = type;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/where', data, { csrfToken });
|
|
2475
|
+
|
|
2476
|
+
// Handle uppercase keys from ABAP
|
|
2477
|
+
const success = result.SUCCESS || result.success;
|
|
2478
|
+
const whereObjects = result.OBJECTS || result.objects || [];
|
|
2479
|
+
const message = result.MESSAGE || result.message || '';
|
|
2480
|
+
const error = result.ERROR || result.error;
|
|
2481
|
+
|
|
2482
|
+
if (!success || error) {
|
|
2483
|
+
console.error(`\n Error: ${error || 'Failed to get where-used list'}`);
|
|
2484
|
+
break;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
if (jsonOutput) {
|
|
2488
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2489
|
+
} else {
|
|
2490
|
+
console.log(`\n ${message}`);
|
|
2491
|
+
console.log('');
|
|
2492
|
+
|
|
2493
|
+
for (let i = 0; i < whereObjects.length; i++) {
|
|
2494
|
+
const obj = whereObjects[i];
|
|
2495
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
2496
|
+
const objType = obj.TYPE || obj.type || '';
|
|
2497
|
+
const error = obj.ERROR || obj.error || '';
|
|
2498
|
+
const references = obj.REFERENCES || obj.references || [];
|
|
2499
|
+
const count = obj.COUNT || obj.count || 0;
|
|
2500
|
+
|
|
2501
|
+
// Handle object not found error
|
|
2502
|
+
if (error) {
|
|
2503
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2504
|
+
console.log(` ${error}`);
|
|
2505
|
+
console.log('');
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
if (count === 0) {
|
|
2510
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2511
|
+
console.log(` No references found`);
|
|
2512
|
+
console.log('');
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
console.log(` 🔍 ${objName} (${objType})`);
|
|
2517
|
+
console.log(` Found ${count} reference(s):`);
|
|
2518
|
+
console.log('');
|
|
2519
|
+
|
|
2520
|
+
// Display references - one line format: include → method (type) or include (type)
|
|
2521
|
+
for (let j = 0; j < references.length; j++) {
|
|
2522
|
+
const ref = references[j];
|
|
2523
|
+
const includeName = ref.INCLUDE_NAME || ref.include_name || '';
|
|
2524
|
+
const includeType = ref.INCLUDE_TYPE || ref.include_type || '';
|
|
2525
|
+
const methodName = ref.METHOD_NAME || ref.method_name || '';
|
|
2526
|
+
|
|
2527
|
+
let line;
|
|
2528
|
+
if (methodName) {
|
|
2529
|
+
line = ` ${j + 1}. ${includeName} → ${methodName} (${includeType})`;
|
|
2530
|
+
} else {
|
|
2531
|
+
line = ` ${j + 1}. ${includeName} (${includeType})`;
|
|
2532
|
+
}
|
|
2533
|
+
console.log(line);
|
|
2534
|
+
}
|
|
2535
|
+
console.log('');
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2442
2541
|
case 'ref': {
|
|
2443
2542
|
const refSearch = require('../src/ref-search');
|
|
2444
2543
|
const topicIndex = args.indexOf('--topic');
|
|
@@ -2564,6 +2663,9 @@ Commands:
|
|
|
2564
2663
|
view --objects <obj1>,<obj2>,... [--type <type>] [--json]
|
|
2565
2664
|
View ABAP object definitions from the ABAP system
|
|
2566
2665
|
|
|
2666
|
+
where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]
|
|
2667
|
+
Find where-used list for ABAP objects (classes, interfaces, programs)
|
|
2668
|
+
|
|
2567
2669
|
ref <pattern> [--json]
|
|
2568
2670
|
Search ABAP reference repositories for patterns. Requires referenceFolder in .abapGitAgent.
|
|
2569
2671
|
|
|
@@ -2610,6 +2712,10 @@ Examples:
|
|
|
2610
2712
|
abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
|
|
2611
2713
|
abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
|
|
2612
2714
|
abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
|
|
2715
|
+
abapgit-agent where --objects ZCL_SUT_AUNIT_RUNNER # Find where class is used
|
|
2716
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE # Find interface implementations
|
|
2717
|
+
abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20 # Limit results
|
|
2718
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE --json # JSON output
|
|
2613
2719
|
abapgit-agent ref "CORRESPONDING" # Search all reference repos
|
|
2614
2720
|
abapgit-agent ref "CX_SY_" # Search exceptions
|
|
2615
2721
|
abapgit-agent ref --topic exceptions # View exception topic
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"start": "node src/server.js",
|
|
17
17
|
"dev": "nodemon src/server.js",
|
|
18
18
|
"test": "jest",
|
|
19
|
+
"test:all": "node scripts/test-all.js",
|
|
20
|
+
"test:jest": "jest",
|
|
21
|
+
"test:aunit": "node scripts/test-all.js --jest --cmd",
|
|
22
|
+
"test:cmd": "node scripts/test-all.js --jest --aunit",
|
|
19
23
|
"pull": "node bin/abapgit-agent",
|
|
20
24
|
"release": "node scripts/release.js",
|
|
21
25
|
"unrelease": "node scripts/unrelease.js"
|
package/src/abap-client.js
CHANGED
|
@@ -459,6 +459,52 @@ class ABAPClient {
|
|
|
459
459
|
|
|
460
460
|
return await this.request('POST', '/list', data, { csrfToken: this.csrfToken });
|
|
461
461
|
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* View ABAP object definitions
|
|
465
|
+
* @param {Array} objects - Array of object names to view
|
|
466
|
+
* @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
|
|
467
|
+
* @returns {object} View result with object definitions
|
|
468
|
+
*/
|
|
469
|
+
async view(objects, type = null) {
|
|
470
|
+
await this.fetchCsrfToken();
|
|
471
|
+
|
|
472
|
+
const data = {
|
|
473
|
+
objects: objects
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
if (type) {
|
|
477
|
+
data.type = type;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
logger.info('Viewing objects', { objects, type, service: 'abapgit-agent' });
|
|
481
|
+
|
|
482
|
+
return await this.request('POST', '/view', data, { csrfToken: this.csrfToken });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Find where-used list for ABAP objects
|
|
487
|
+
* @param {Array} objects - Array of object names to search
|
|
488
|
+
* @param {string} type - Object type (optional)
|
|
489
|
+
* @param {number} limit - Maximum results (default: 100, max: 500)
|
|
490
|
+
* @returns {object} Where-used result with found objects
|
|
491
|
+
*/
|
|
492
|
+
async where(objects, type = null, limit = 100) {
|
|
493
|
+
await this.fetchCsrfToken();
|
|
494
|
+
|
|
495
|
+
const data = {
|
|
496
|
+
objects: objects,
|
|
497
|
+
limit: Math.min(Math.max(1, limit), 500)
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
if (type) {
|
|
501
|
+
data.type = type;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
logger.info('Finding where-used', { objects, type, limit: data.limit, service: 'abapgit-agent' });
|
|
505
|
+
|
|
506
|
+
return await this.request('POST', '/where', data, { csrfToken: this.csrfToken });
|
|
507
|
+
}
|
|
462
508
|
}
|
|
463
509
|
|
|
464
510
|
// Singleton instance
|
package/src/agent.js
CHANGED
|
@@ -264,6 +264,54 @@ class ABAPGitAgent {
|
|
|
264
264
|
throw new Error(`List command failed: ${error.message}`);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* View ABAP object definitions
|
|
270
|
+
* @param {Array} objects - Array of object names to view
|
|
271
|
+
* @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
|
|
272
|
+
* @returns {object} View result with object definitions
|
|
273
|
+
*/
|
|
274
|
+
async view(objects, type = null) {
|
|
275
|
+
logger.info('Viewing objects', { objects, type });
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const result = await this.abap.view(objects, type);
|
|
279
|
+
return {
|
|
280
|
+
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
281
|
+
command: result.COMMAND || result.command || 'VIEW',
|
|
282
|
+
objects: result.OBJECTS || result.objects || [],
|
|
283
|
+
error: result.ERROR || result.error || null
|
|
284
|
+
};
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('View command failed', { error: error.message });
|
|
287
|
+
throw new Error(`View command failed: ${error.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Find where-used list for ABAP objects
|
|
293
|
+
* @param {Array} objects - Array of object names to search
|
|
294
|
+
* @param {string} type - Object type (optional)
|
|
295
|
+
* @param {number} limit - Maximum results (default: 100, max: 500)
|
|
296
|
+
* @returns {object} Where-used result with found objects
|
|
297
|
+
*/
|
|
298
|
+
async where(objects, type = null, limit = 100) {
|
|
299
|
+
logger.info('Finding where-used', { objects, type, limit });
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const result = await this.abap.where(objects, type, limit);
|
|
303
|
+
return {
|
|
304
|
+
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
305
|
+
command: result.COMMAND || result.command || 'WHERE',
|
|
306
|
+
objects: result.OBJECTS || result.objects || [],
|
|
307
|
+
message: result.MESSAGE || result.message || '',
|
|
308
|
+
error: result.ERROR || result.error || null
|
|
309
|
+
};
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logger.error('Where command failed', { error: error.message });
|
|
312
|
+
throw new Error(`Where command failed: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
267
315
|
}
|
|
268
316
|
|
|
269
317
|
module.exports = {
|