abapgit-agent 1.19.0 → 1.19.2
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.
|
@@ -6,9 +6,9 @@ parent: ABAP Coding Guidelines
|
|
|
6
6
|
grand_parent: ABAP Development
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# abapGit XML Metadata — XML-Only Objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG, SUSO)
|
|
9
|
+
# abapGit XML Metadata — XML-Only Objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG, SUSO, PINF)
|
|
10
10
|
|
|
11
|
-
XML templates for ABAP Dictionary objects that have no `.abap` source file — TABL, STRU, DTEL, TTYP, DOMA, message classes (MSAG),
|
|
11
|
+
XML templates for ABAP Dictionary objects that have no `.abap` source file — TABL, STRU, DTEL, TTYP, DOMA, message classes (MSAG), authorization objects (SUSO), and package interfaces (PINF).
|
|
12
12
|
|
|
13
13
|
> **CRITICAL: Always write XML files with a UTF-8 BOM (`\ufeff`) as the very first character**, before `<?xml ...`.
|
|
14
14
|
> Without the BOM, abapGit shows the object as **"M" (modified)** after every pull.
|
|
@@ -19,7 +19,7 @@ XML templates for ABAP Dictionary objects that have no `.abap` source file — T
|
|
|
19
19
|
> **For CLAS, INTF, PROG, DDLS, DCLS, FUGR** (have source files): see `abapgit-agent ref --topic abapgit`
|
|
20
20
|
> **For DDLS, DCLS** (CDS — have source files): also see `abapgit-agent ref --topic abapgit`
|
|
21
21
|
|
|
22
|
-
**Searchable keywords**: table xml, structure xml, data element xml, table type xml, domain xml, message class xml, tabl, stru, dtel, ttyp, doma, msag, suso, authorization object, dictionary
|
|
22
|
+
**Searchable keywords**: table xml, structure xml, data element xml, table type xml, domain xml, message class xml, tabl, stru, dtel, ttyp, doma, msag, suso, authorization object, dictionary, pinf, package interface
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -511,3 +511,52 @@ abapGit uses the view `DD01V` as the root element (not `DD01L`). The serializer
|
|
|
511
511
|
**Note**: Authorization objects (SUSO) are the SU21 objects that group authorization fields (AUTH/SU20)
|
|
512
512
|
together. An object like `AUD_SCOPEM` uses fields `ACTVT`, `BO_SERVICE`, and `AUD_GROUP`.
|
|
513
513
|
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### Package Interface (PINF)
|
|
517
|
+
|
|
518
|
+
**Filename**: `src/zmy_pinf.pinf.xml`
|
|
519
|
+
|
|
520
|
+
> Pull with: `pull --files src/zmy_pinf.pinf.xml`
|
|
521
|
+
>
|
|
522
|
+
> View with: `view --objects ZMY_PINF --type PINF`
|
|
523
|
+
|
|
524
|
+
A package interface declares which objects a package makes visible to other packages — it is the "API" of an ABAP package. Packages that use these objects must depend on the package that owns the interface.
|
|
525
|
+
|
|
526
|
+
```xml
|
|
527
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
528
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_PINF" serializer_version="v1.0.0">
|
|
529
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
530
|
+
<asx:values>
|
|
531
|
+
<PINF>
|
|
532
|
+
<ATTRIBUTES>
|
|
533
|
+
<INTF_NAME>ZMY_PINF</INTF_NAME>
|
|
534
|
+
<DESCRIPT>Package interface for ZMY</DESCRIPT>
|
|
535
|
+
</ATTRIBUTES>
|
|
536
|
+
<ELEMENTS>
|
|
537
|
+
<SCOMELDTLN>
|
|
538
|
+
<INTF_NAME>ZMY_PINF</INTF_NAME>
|
|
539
|
+
<ELEM_TYPE>CLAS</ELEM_TYPE>
|
|
540
|
+
<ELEM_KEY>ZCL_MY_CLASS</ELEM_KEY>
|
|
541
|
+
</SCOMELDTLN>
|
|
542
|
+
<SCOMELDTLN>
|
|
543
|
+
<INTF_NAME>ZMY_PINF</INTF_NAME>
|
|
544
|
+
<ELEM_TYPE>INTF</ELEM_TYPE>
|
|
545
|
+
<ELEM_KEY>ZIF_MY_INTERFACE</ELEM_KEY>
|
|
546
|
+
</SCOMELDTLN>
|
|
547
|
+
</ELEMENTS>
|
|
548
|
+
</PINF>
|
|
549
|
+
</asx:values>
|
|
550
|
+
</asx:abap>
|
|
551
|
+
</abapGit>
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**Key Fields**:
|
|
555
|
+
- `ATTRIBUTES/INTF_NAME`: Package interface name (max 30 chars, stored in `SCOMPARAMET.INTF_NAME`)
|
|
556
|
+
- `ATTRIBUTES/DESCRIPT`: Description text
|
|
557
|
+
- `ELEMENTS/SCOMELDTLN`: One entry per exported object; each has:
|
|
558
|
+
- `INTF_NAME`: Same as the interface name (repeated on every row)
|
|
559
|
+
- `ELEM_TYPE`: Object type — `CLAS`, `INTF`, `DTEL`, `TABL`, `STRU`, `TTYP`, `DOMA`, `SUSO`, `ENQU`, `STOB`, `VIEW`
|
|
560
|
+
- `ELEM_KEY`: Object name
|
|
561
|
+
|
|
562
|
+
**Note**: No UTF-8 BOM — unlike TABL/DTEL/MSAG, the PINF serializer (`LCL_OBJECT_PINF`) does not write a BOM. Do not add `\ufeff` before `<?xml ...>`.
|
|
@@ -42,6 +42,8 @@ grand_parent: ABAP Development
|
|
|
42
42
|
| Local variable name | **30** |
|
|
43
43
|
| Local type/class name | **30** |
|
|
44
44
|
| Test class name (local) | **30** |
|
|
45
|
+
| Package Interface name (PINF) | **30** |
|
|
46
|
+
| **Authorization Object name (SUSO)** | **10** |
|
|
45
47
|
|
|
46
48
|
---
|
|
47
49
|
|
|
@@ -150,6 +152,8 @@ These limits come from the ABAP Dictionary (DDIC) and ABAP kernel:
|
|
|
150
152
|
| 40 chars (CDS names) | CDS objects stored in `DD02L.TABNAME CHAR(40)` — intentionally larger for CDS |
|
|
151
153
|
| 20 chars (MSAG) | Message class name stored in `T100A.ARBGB CHAR(20)` |
|
|
152
154
|
| 26 chars (FUGR) | Function group internally prefixed with `SAPL` (4 chars) for the main include |
|
|
155
|
+
| **10 chars (SUSO)** | Authorization object name stored in `TOBJ.OBJCT CHAR(10)` — notably shorter than other types |
|
|
156
|
+
| 30 chars (PINF) | Package interface name stored in `SCOMPARAMET.INTF_NAME CHAR(30)` |
|
|
153
157
|
|
|
154
158
|
---
|
|
155
159
|
|
package/package.json
CHANGED
package/src/commands/drop.js
CHANGED
|
@@ -8,7 +8,8 @@ const { printHttpError } = require('../utils/format-error');
|
|
|
8
8
|
const EXT_TO_TYPE = {
|
|
9
9
|
clas: 'CLAS', intf: 'INTF', prog: 'PROG', fugr: 'FUGR',
|
|
10
10
|
tabl: 'TABL', dtel: 'DTEL', ttyp: 'TTYP', doma: 'DOMA',
|
|
11
|
-
ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU', enho: 'ENHO'
|
|
11
|
+
ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU', enho: 'ENHO',
|
|
12
|
+
suso: 'SUSO'
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
/**
|
package/src/commands/inspect.js
CHANGED
|
@@ -48,8 +48,10 @@ function buildInspectJUnit(results) {
|
|
|
48
48
|
|
|
49
49
|
const testcases = [];
|
|
50
50
|
|
|
51
|
+
const inspectClass = `${escapeXml(objectName)}.Inspect`;
|
|
52
|
+
|
|
51
53
|
if (errorCount === 0 && warnCount === 0) {
|
|
52
|
-
testcases.push(` <testcase name="
|
|
54
|
+
testcases.push(` <testcase name="Syntax check" classname="${inspectClass}"/>`);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
for (const err of errors) {
|
|
@@ -67,9 +69,9 @@ function buildInspectJUnit(results) {
|
|
|
67
69
|
text
|
|
68
70
|
].filter(Boolean).join('\n');
|
|
69
71
|
const checkId = checkClass && checkCode ? `${checkClass}/${checkCode}` : null;
|
|
70
|
-
const caseName = checkId ?
|
|
72
|
+
const caseName = checkId ? checkId : (methodName ? `${methodName} line ${line}` : `Line ${line}`);
|
|
71
73
|
testcases.push(
|
|
72
|
-
` <testcase name="${escapeXml(caseName)}" classname="${
|
|
74
|
+
` <testcase name="${escapeXml(caseName)}" classname="${inspectClass}">\n` +
|
|
73
75
|
` <failure type="SyntaxError" message="${escapeXml(text)}">${escapeXml(detail)}</failure>\n` +
|
|
74
76
|
` </testcase>`
|
|
75
77
|
);
|
|
@@ -89,9 +91,9 @@ function buildInspectJUnit(results) {
|
|
|
89
91
|
text
|
|
90
92
|
].filter(Boolean).join('\n');
|
|
91
93
|
const checkId = checkClass && checkCode ? `${checkClass}/${checkCode}` : null;
|
|
92
|
-
const caseName = checkId ?
|
|
94
|
+
const caseName = checkId ? checkId : (methodName ? `${methodName} line ${line}` : `Line ${line}`);
|
|
93
95
|
testcases.push(
|
|
94
|
-
` <testcase name="${escapeXml(caseName)}" classname="${
|
|
96
|
+
` <testcase name="${escapeXml(caseName)}" classname="${inspectClass}">\n` +
|
|
95
97
|
` <failure type="Warning" message="${escapeXml(text)}">${escapeXml(detail)}</failure>\n` +
|
|
96
98
|
` </testcase>`
|
|
97
99
|
);
|
|
@@ -427,6 +429,20 @@ Examples:
|
|
|
427
429
|
|
|
428
430
|
let filesSyntaxCheck = [...new Set(args[filesArgIndex + 1].split(',').map(f => f.trim()))];
|
|
429
431
|
|
|
432
|
+
// Skip XML-only object types that have no ABAP source to inspect
|
|
433
|
+
const XML_ONLY_EXTENSIONS = [
|
|
434
|
+
'.pinf.xml', '.suso.xml', '.msag.xml', '.doma.xml',
|
|
435
|
+
'.dtel.xml', '.ttyp.xml', '.tabl.xml',
|
|
436
|
+
];
|
|
437
|
+
const beforeXmlOnly = filesSyntaxCheck.length;
|
|
438
|
+
filesSyntaxCheck = filesSyntaxCheck.filter(f => {
|
|
439
|
+
const lower = pathModule.basename(f).toLowerCase();
|
|
440
|
+
return !XML_ONLY_EXTENSIONS.some(ext => lower.endsWith(ext));
|
|
441
|
+
});
|
|
442
|
+
if (beforeXmlOnly > filesSyntaxCheck.length && !args.includes('--json')) {
|
|
443
|
+
console.log(` Skipped ${beforeXmlOnly - filesSyntaxCheck.length} XML-only file(s) (no source to inspect)`);
|
|
444
|
+
}
|
|
445
|
+
|
|
430
446
|
// Parse optional --variant parameter; fall back to project config
|
|
431
447
|
const variantArgIndex = args.indexOf('--variant');
|
|
432
448
|
const variantArg = variantArgIndex !== -1 ? args[variantArgIndex + 1] : null;
|
package/src/commands/unit.js
CHANGED
|
@@ -158,6 +158,9 @@ function buildUnitJUnit(results) {
|
|
|
158
158
|
const failedMethods = methods.filter(m => !m.passed);
|
|
159
159
|
const syntheticFailures = thresholdFailure ? 1 : 0;
|
|
160
160
|
const totalFailures = failedMethods.length + syntheticFailures;
|
|
161
|
+
// classname "ZCL_MY_TEST.LTCL_UNIT_TEST" groups under the same ABAP class node
|
|
162
|
+
// as "ZCL_MY_TEST.Inspect" from the inspect report — one node per ABAP class.
|
|
163
|
+
const suiteLabel = className;
|
|
161
164
|
|
|
162
165
|
const lines = [];
|
|
163
166
|
|
|
@@ -170,13 +173,12 @@ function buildUnitJUnit(results) {
|
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
if (testCount === 0) {
|
|
173
|
-
lines.push(` <testcase name="(no tests)" classname="${escapeXml(
|
|
176
|
+
lines.push(` <testcase name="(no tests)" classname="${escapeXml(suiteLabel)}"/>`);
|
|
174
177
|
} else {
|
|
175
178
|
for (const m of methods) {
|
|
176
179
|
const timeAttr = m.executionTime != null ? ` time="${m.executionTime}"` : '';
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
const testClass = m.testClassName ? `${className}.${m.testClassName}` : className;
|
|
180
|
+
// "Unit Tests / ZCL_MY_TEST.LTCL_UNIT_TEST" groups by namespace → class → local test class.
|
|
181
|
+
const testClass = m.testClassName ? `${suiteLabel}.${m.testClassName}` : suiteLabel;
|
|
180
182
|
if (m.passed) {
|
|
181
183
|
lines.push(` <testcase name="${escapeXml(m.name)}" classname="${escapeXml(testClass)}"${timeAttr}/>`);
|
|
182
184
|
} else {
|
|
@@ -195,14 +197,14 @@ function buildUnitJUnit(results) {
|
|
|
195
197
|
|
|
196
198
|
if (thresholdFailure) {
|
|
197
199
|
lines.push(
|
|
198
|
-
` <testcase name="coverage_threshold" classname="${escapeXml(
|
|
200
|
+
` <testcase name="coverage_threshold" classname="${escapeXml(suiteLabel)}">\n` +
|
|
199
201
|
` <failure type="FAILURE" message="${escapeXml(thresholdFailure)}">${escapeXml(thresholdFailure)}</failure>\n` +
|
|
200
202
|
` </testcase>`
|
|
201
203
|
);
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
return (
|
|
205
|
-
` <testsuite name="${escapeXml(
|
|
207
|
+
` <testsuite name="${escapeXml(suiteLabel)}" ` +
|
|
206
208
|
`tests="${Math.max(testCount + syntheticFailures, 1)}" failures="${totalFailures}" errors="0">\n` +
|
|
207
209
|
lines.join('\n') + '\n' +
|
|
208
210
|
` </testsuite>`
|
package/src/commands/view.js
CHANGED
|
@@ -593,6 +593,50 @@ Examples:
|
|
|
593
593
|
console.log(` ${desc}`);
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
|
+
} else if (objType === 'PINF' || objType === 'Package Interface') {
|
|
597
|
+
// PINF: description box, then members grouped by type
|
|
598
|
+
const description = obj.DESCRIPTION || obj.description || '';
|
|
599
|
+
const devclass = obj.DEVCLASS || obj.devclass || '';
|
|
600
|
+
const boxWidth = 50;
|
|
601
|
+
const boxTop = ' ┌' + '─'.repeat(boxWidth) + '┐';
|
|
602
|
+
const boxBot = ' └' + '─'.repeat(boxWidth) + '┘';
|
|
603
|
+
const boxRow = (text) => ' │ ' + String(text || '').substring(0, boxWidth - 2).padEnd(boxWidth - 2) + ' │';
|
|
604
|
+
|
|
605
|
+
console.log(` PACKAGE INTERFACE ${objName}:`);
|
|
606
|
+
console.log(boxTop);
|
|
607
|
+
if (description) console.log(boxRow(description));
|
|
608
|
+
if (devclass) console.log(boxRow(`Package: ${devclass}`));
|
|
609
|
+
console.log(boxBot);
|
|
610
|
+
|
|
611
|
+
// Parse components: DESCRIPTION = "ELEM_TYPE: ELEM_KEY"
|
|
612
|
+
const members = components.map(c => {
|
|
613
|
+
const desc = String(c.DESCRIPTION || c.description || '');
|
|
614
|
+
const colonIdx = desc.indexOf(': ');
|
|
615
|
+
return colonIdx !== -1
|
|
616
|
+
? { type: desc.substring(0, colonIdx), key: desc.substring(colonIdx + 2) }
|
|
617
|
+
: { type: '?', key: desc };
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
if (members.length > 0) {
|
|
621
|
+
// Group by type, preserving order of first occurrence
|
|
622
|
+
const groups = [];
|
|
623
|
+
const seen = new Map();
|
|
624
|
+
for (const m of members) {
|
|
625
|
+
if (!seen.has(m.type)) { seen.set(m.type, []); groups.push(m.type); }
|
|
626
|
+
seen.get(m.type).push(m.key);
|
|
627
|
+
}
|
|
628
|
+
console.log('');
|
|
629
|
+
console.log(` Members (${members.length}):`);
|
|
630
|
+
for (const type of groups) {
|
|
631
|
+
for (const key of seen.get(type)) {
|
|
632
|
+
console.log(` ${type.padEnd(6)} ${key}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log('');
|
|
637
|
+
console.log(' (no members)');
|
|
638
|
+
}
|
|
639
|
+
|
|
596
640
|
} else if (objType === 'SUSO' || objType === 'Authorization Object') {
|
|
597
641
|
// SUSO: property box for object class + fields, then activities list
|
|
598
642
|
const classComp = components.filter(c => String(c.FIELD || c.field || '').startsWith('OCLSS'));
|