Language Atlas (mini-courses for more languages) Module 2 – ABAP with the open-source abaplint toolchain
ABAP without an SAP system: setup and limits
Run ABAP Unit tests on Node.js with the pinned abaplint transpiler, lay out abapGit files, and see by measurement where it differs from an SAP system.
What you will learn
- Explain what ABAP is used for and which parts of it run outside an SAP system
- Install Node.js 24 and the pinned abaplint packages, then run ABAP Unit tests from the command line
- Organise ABAP source in abapGit format so linters, transpilers and SAP systems read the same files
- Read a failing ABAP Unit run and name the test, the expected value and the actual value
- Rewrite newer ABAP syntax as 7.02 with abaplint's downport rule and test the result
Before you start
What you’ll learn · Before you start
What you will learn
- Explain what ABAP is used for and which parts of it run outside an SAP system
- Install Node.js 24 and the pinned abaplint packages, then run ABAP Unit tests from the command line
- Organise ABAP source in abapGit format so linters, transpilers and SAP systems read the same files
- Read a failing ABAP Unit run and name the test, the expected value and the actual value
- Rewrite newer ABAP syntax as 7.02 with abaplint's downport rule and test the result
Before you start
On this page
ABAP is the language of SAP’s application servers, and you cannot install one of those on a laptop in an afternoon. Three MIT-licensed npm packages close most of that gap: on Node.js 24, two commands turned this lesson’s class, its test class and a library of standard classes into 584 JavaScript modules and ran three ABAP Unit tests.
This lesson sets that toolchain up at pinned versions, lays the source out the way abapGit stores it, reads a passing and a failing test run, and measures where the transpiler: A compiler whose output is source code in another high-level language rather than machine code. The abaplint transpiler turns ABAP into JavaScript, which Node.js then runs. More in “How the Language Atlas works” gives another answer than an SAP system would, so that you know which results to trust before the module teaches the language itself.
ABAP runs inside SAP systems, and a large part of it runs on Node.js
SAP’s keyword documentation calls ABAP the programming interface of the Application Server ABAP, used for system programs and for application programs that are usually built on objects of the ABAP Dictionary or of CDS. The on-premise releases of SAP S/4HANA run on that server: S/4HANA 2022 uses ABAP release 7.57, the ABAP Platform 2022. If you maintain ABAP, your code lives in such a system next to SAP’s own, and every change normally starts with a logon to a development system.
The abaplint transpiler takes another route. It reads ABAP source files, checks them with abaplint’s parser and writes
JavaScript that Node.js runs with the @abaplint/runtime package. The standard classes your code calls, such as
cl_abap_unit_assert, the assertion class of ABAP Unit: ABAP's built-in unit test framework: local test classes declared FOR TESTING, whose test methods check results with the methods of the class cl_abap_unit_assert, such as assert_equals., come from open-abap-core, an
MIT-licensed library written in ABAP that is transpiled together with your files. The transpiler’s README names
abapGit, abap2UI5 and SAP’s abap-file-formats-tools as projects that run their unit tests this way on every push, and
Exercism’s ABAP test runner uses it to run learners’ solutions.
Step through one run of this lesson’s first project, with the counts its session printed:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Your files. npx abap_transpile starts with your own source: the hello class, its test class and the XML file that describes the class, three files in the src folder named in abap_transpile.json.
- The library. Next it reads open-abap-core from its folder, 678 files at the pinned commit. They hold the standard classes your code calls, such as cl_abap_unit_assert, written in ABAP and turned into JavaScript like your own.
- Parsed and checked. The transpiler parses every file with the abaplint parser and checks its syntax. A syntax error stops the run here, before any JavaScript is written.
- JavaScript written. It writes one JavaScript module per object, 584 of them for this project, into output, plus index.mjs, the script that runs every test class it found.
- The tests run. node output/index.mjs loads the runtime, then runs each test method in turn and prints a line before each one: ZCL_ATLAS_HELLO: running ltcl_greet->greets_by_name, then the other two.
- The exit status. When every test passes the script exits with status 0. The first failing test ends the run with status 1, which is what a build server checks.
- When a test fails. A failing test sends you back to the source files: fix the class or the expectation, then run both commands again. The output folder is rebuilt from the files every time.
What the transpiler cannot give you is the SAP system around the code: its screens, its database tables and their data, its users and their authorisations, its connections to other systems. The last section of this lesson runs a few of those and shows what happens. The rest of what this module teaches (data, internal tables, classes, exceptions, unit tests and SQL on a small database) runs here, and every result that would differ on an SAP system is named where it appears.
Not affiliated
Based on public information about SAP. MySmartCoPilot is not affiliated with SAP, and SAP’s real systems may differ from what this lesson describes.
Checkpoint
Check: Where ABAP runs, and what the transpiler runs
Setup: Node.js 24, three pinned packages and one library commit
The setup takes four steps, and each one pins a version, because the transpiler changes fast: in the 30 days up to 2.13.98, the release this module’s runs were recorded with, its command-line package was published 24 times. A result recorded with one version promises nothing about the next, which is why every session of this module starts by listing its versions.
- Install Node.js 24. The transpiler’s README asks for Node.js 22 or newer; this module’s sessions ran on 24.21.0.
- Write package.json with exact versions and run
npm install, which writespackage-lock.jsonbeside it. Commit both: on a build server,npm ciinstalls exactly what the lockfile lists. - Clone open-abap-core and check out the pinned commit:
git clone https://github.com/open-abap/open-abap-core.git, thengit -C open-abap-core checkout dd59b82c166b09f89ce9bc668aa7a8af689075a3. - Write abap_transpile.json, which tells the transpiler where your source is, where to write and where the library is.
hello/package.json
{
"name": "atlas-hello",
"private": true,
"devDependencies": {
"@abaplint/cli": "2.120.68",
"@abaplint/runtime": "2.13.98",
"@abaplint/transpiler-cli": "2.13.98"
}
} hello/abap_transpile.json
{
"input_folder": "src",
"output_folder": "output",
"libs": [{ "folder": "open-abap-core" }],
"write_unit_tests": true,
"options": { "addCommonJS": true }
} | Setting | What it does |
|---|---|
"input_folder": "src" |
Where your ABAP files are |
"output_folder": "output" |
Where the JavaScript goes; it is written again on every run |
"libs": [{ "folder": "open-abap-core" }] |
Reads the library from the folder you cloned. With a url instead, the transpiler clones the newest commit on every run, so your results could change without any change of yours |
"write_unit_tests": true |
Also writes output/index.mjs, the script that runs every test class |
"options": { "addCommonJS": true } |
Writes each object as a module that imports what it uses |
The first run of a project checks the versions, transpiles and runs the tests:
from session import run
# The first run of a new ABAP project: Node.js and the pinned packages say their versions, the transpiler turns the
# class, its test class and the open-abap-core library into JavaScript, and Node.js runs the three ABAP Unit tests.
run("""
node --version
cd hello
npm ls
npx abap_transpile
node output/index.mjs
""") Output
$ node --version v24.21.0 $ cd hello $ npm ls atlas-hello@ . ├── @abaplint/cli@2.120.68 ├── @abaplint/runtime@2.13.98 └── @abaplint/transpiler-cli@2.13.98 $ npx abap_transpile Transpiler CLI Using config: abap_transpile.json 3 files added from source 0 files skipped in source Building From folder: open-abap-core 678 files added from lib Output 584 objects written to disk $ node output/index.mjs ZCL_ATLAS_HELLO: running ltcl_greet->greets_by_name ZCL_ATLAS_HELLO: running ltcl_greet->trims_spaces ZCL_ATLAS_HELLO: running ltcl_greet->greets_nobody
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 setup_session.py
Read it from the top. node answered v24.21.0 and npm ls listed the three pinned packages: that is the toolchain
check to repeat whenever a result surprises you. The transpiler took 3 files from src and 678 from the library, then
wrote 584 objects. Each of the next three lines announces one test method before it runs. No error after them and no
exit status line mean that all three passed and the run ended with status 0.
An options object without addCommonJS
When a project has an abap_transpile.json, the command uses its options as they stand, so "options": {} leaves
addCommonJS off. The transpile still reports 584 objects, but the test run stops at once with
ReferenceError: cl_abap_objectdescr is not defined, because the class modules no longer import the classes they
build on. Keep "addCommonJS": true in every options you write.
Checkpoint
Check: A pinned setup and the first run
One ABAP class is three files in abapGit format
abapGit: A Git client for ABAP, written in ABAP and run inside an SAP system. It stores each ABAP object in a repository as files named after the object and its type, such as zcl_atlas_hello.clas.abap. stores every object under one naming rule: the object’s name in lower case, its type, an optional part of the object and the extension. The hello class therefore has three files:
hello/src/zcl_atlas_hello.clas.abap
CLASS zcl_atlas_hello DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS greet
IMPORTING iv_name TYPE string
RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.
CLASS zcl_atlas_hello IMPLEMENTATION.
METHOD greet.
DATA(lv_name) = condense( iv_name ).
IF lv_name IS INITIAL.
rv_text = `Hello!`.
ELSE.
rv_text = |Hello, { lv_name }!|.
ENDIF.
ENDMETHOD.
ENDCLASS. hello/src/zcl_atlas_hello.clas.testclasses.abap
CLASS ltcl_greet DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_hello TYPE REF TO zcl_atlas_hello.
METHODS setup.
METHODS greets_by_name FOR TESTING.
METHODS trims_spaces FOR TESTING.
METHODS greets_nobody FOR TESTING.
ENDCLASS.
CLASS ltcl_greet IMPLEMENTATION.
METHOD setup.
mo_hello = NEW zcl_atlas_hello( ).
ENDMETHOD.
METHOD greets_by_name.
cl_abap_unit_assert=>assert_equals( act = mo_hello->greet( `Asha` )
exp = `Hello, Asha!` ).
ENDMETHOD.
METHOD trims_spaces.
cl_abap_unit_assert=>assert_equals( act = mo_hello->greet( ` Ravi ` )
exp = `Hello, Ravi!` ).
ENDMETHOD.
METHOD greets_nobody.
cl_abap_unit_assert=>assert_equals( act = mo_hello->greet( `` )
exp = `Hello!` ).
ENDMETHOD.
ENDCLASS. hello/src/zcl_atlas_hello.clas.xml
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_ATLAS_HELLO</CLSNAME>
<LANGU>E</LANGU>
<DESCRIPT>Atlas: a first class with unit tests</DESCRIPT>
<STATE>1</STATE>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit> The source file holds the class definition and its implementation. The test classes file holds the local test class
ltcl_greet, declared FOR TESTING, which production code cannot address and production systems do not generate,
according to SAP’s documentation. The XML file holds what an SAP system keeps beside the source, such as the class
name, its description and the flags FIXPT (fixed point arithmetic on) and WITH_UNIT_TESTS. A REPORT program has
two files, .prog.abap and
.prog.xml, and a database table has only its XML file, because it has no source code. A namespace at the start of a
name becomes two # characters: abapGit’s documentation stores /FOOBAR/REPORT as #foobar#report.prog.abap.
The same three files serve three tools: abaplint checks them, the transpiler runs them, and abapGit pulls them into an SAP system as a real class. Around them, a project folder holds three kinds of content, set apart by who writes each:
One ABAP project folder, by who writes each part
Text description of the diagram
The diagram shows the hello project folder as three boxes, from top to bottom, in the order a first run makes them.
- You write and commit: the src folder with the class zcl_atlas_hello.clas.abap, its test classes zcl_atlas_hello.clas.testclasses.abap and its descriptor zcl_atlas_hello.clas.xml, the configuration files package.json and abap_transpile.json, and package-lock.json, the lockfile that npm install writes beside package.json.
- Installed at pinned versions, by npm ci and git clone: node_modules with the three pinned packages, and open-abap-core, the library checked out at commit dd59b82.
- Written by npx abap_transpile: the output folder with 584 JavaScript modules and index.mjs, which runs the tests.
Commit what you write, pin what you install and never commit output: the transpiler writes it again from your files
on every run.
Checkpoint
Check: ABAP objects as abapGit files
A failing test names itself, both values and exit status 1
A passing run says little; a failing one says exactly where to look. This copy of the project has one wrong expectation, a full stop where the class writes an exclamation mark. The transpile log goes to a file, to keep the session short:
from session import run
# The same project with one wrong expectation in the first test. The transpile log goes to a file to keep the session
# short; the test run stops at the failing test and exits with status 1.
run("""
cd hello-wrong
npx abap_transpile > transpile.log
!node output/index.mjs
""") Output
$ cd hello-wrong
$ npx abap_transpile > transpile.log
$ node output/index.mjs
ZCL_ATLAS_HELLO: running ltcl_greet->greets_by_name
<ref *1> kernel_cx_assert [Error]
at cl_abap_unit_assert.assert_equals (file://output/cl_abap_unit_assert.clas.mjs:417:31)
at async #greets_by_name (file://output/zcl_atlas_hello.clas.testclasses.mjs:35:5)
at async run (file://output/index.mjs:36:9) {
me: ABAPObject {
value: [Circular *1],
qualifiedName: undefined,
RTTIName: undefined
},
INTERNAL_ID: 5,
FRIENDS_ACCESS_INSTANCE: <ref *2> { SUPER: [Circular *2] },
previous: ABAPObject {
value: undefined,
qualifiedName: 'CX_ROOT',
RTTIName: '\\CLASS=CX_ROOT'
},
textid: Character {
value: ' ',
constant: false,
numeric: undefined,
length: 32,
extra: {}
},
kernel_errid: Character {
value: ' ',
constant: false,
numeric: undefined,
length: 30,
extra: { qualifiedName: 'CHAR30', ddicName: 'CHAR30', description: '' }
},
get_longtext: [AsyncFunction: if_message$get_longtext],
get_text: [AsyncFunction: if_message$get_text],
actual: String { value: 'Hello, Asha!', qualifiedName: 'STRING' },
expected: String { value: 'Hello, Asha.', qualifiedName: 'STRING' },
msg: String {
value: "Expected 'Hello, Asha.', got 'Hello, Asha!'",
qualifiedName: 'STRING'
},
EXTRA_CX: {
INTERNAL_FILENAME: 'cl_abap_unit_assert.clas.abap',
INTERNAL_LINE: 467
}
}
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 failing_session.py
Three parts of that long output matter. The line before it, ZCL_ATLAS_HELLO: running ltcl_greet->greets_by_name,
names the class and the test method that failed. Near the end, actual holds what the code returned,
Hello, Asha!, expected holds what the test asked for, Hello, Asha., and msg puts both in one sentence. The
last line, [exit status 1], is what a build server reads. Between them is the JavaScript exception that carries
those values, with fields such as INTERNAL_ID that you can skip.
Look at what is missing too: trims_spaces and greets_nobody, the other two test methods, never ran. This runner
stops at the first failing test, so one failure tells you nothing about the tests after it.
Counting running lines as passes
A running line is printed before a test starts, not after it passes. Two lines followed by an error mean one pass
and one failure, and the tests after the failure did not run at all. Only exit status 0 means every test passed:
fix the first failure, run again, and repeat until the status is 0.
Checkpoint
Check: Reading a failing ABAP Unit run
The transpiler reads 7.02 syntax, and downport rewrites newer code
ABAP grew release by release, and code written for one system may use syntax that an older one cannot read. Walk through the releases whose syntax this module meets:
- 7.02
String templates and the && operator transpiler input
Release 7.0 with enhancement package 2 brought string templates such as |Hello, { lv_name }!|, the concatenation operator && and the decimal floating point types decfloat16 and decfloat34. The transpiler's README asks for this level of syntax as its input.
- 7.40 SP02
Inline declarations, NEW and table expressions downport rewrites
Inline declarations such as DATA(lv_name) = …, constructor operators such as NEW, VALUE and CONV, and table expressions such as lt_orders[ id = 1 ] arrived. abaplint's downport rule rewrites them as 7.02 statements, and the pinned transpiler also accepted the ones this module uses.
- 7.40 SP05
@ in front of host variables
ABAP SQL statements began to mark ABAP data objects with @, as in WHERE customer = @lv_customer. A statement that uses @ is checked in a strict mode.
- 7.50
The int8 type, and Unicode only
The 8-byte integer type int8 arrived, and from 7.50 the application server runs only as a Unicode system.
- 7.57
ABAP Platform 2022
Release 7.57 is the ABAP Platform 2022, which SAP S/4HANA 2022 on premise runs on. It is the edition of SAP's keyword documentation that this course cites.
The timeline has 5 events, from 7.02 to 7.57.
The transpiler’s README asks for 7.02 syntax and adds that many 7.50 statements and expressions are supported too;
the pinned version accepted every 7.40 construct this module uses, such as NEW and inline DATA( ). When code has
to run on a 7.02 system, or a tool insists on 7.02, abaplint’s
downport: Rewriting ABAP written in newer syntax as equivalent older syntax, such as 7.02, so that older systems and tools accept it. abaplint's downport rule does it for you when the target version is v702.
rule rewrites it. Its target is always 7.02, so the rule runs only when abaplint.json sets the syntax version v702:
from session import run
# abaplint's downport rule, set to ABAP 7.02 in abaplint.json, finds the newer syntax in the hello class and rewrites
# it with --fix; the rewritten class still passes its tests.
run("""
cd downport
!npx abaplint
npx abaplint --fix
cat src/zcl_atlas_hello.clas.abap
npx abap_transpile > transpile.log
node output/index.mjs
""") Output
$ cd downport
$ npx abaplint
abaplint 2.120.68
Using config: abaplint.json
ABAP release: v702 (SAP_BASIS 702, kernel 702, on-premise 702, cloud n/a, CE n/a)
ABAP language version: Normal
Using dependency from: ./open-abap-core/src/**/*.*
src/zcl_atlas_hello.clas.abap[10, 5] - Outline DATA (downport) [E]
src/zcl_atlas_hello.clas.testclasses.abap[13, 5] - Use CREATE OBJECT instead of NEW (downport) [E]
abaplint: 2 issue(s) found, 3 file(s) analyzed
[exit status 1]
$ npx abaplint --fix
abaplint 2.120.68
Using config: abaplint.json
ABAP release: v702 (SAP_BASIS 702, kernel 702, on-premise 702, cloud n/a, CE n/a)
ABAP language version: Normal
Using dependency from: ./open-abap-core/src/**/*.*
Iteration 1, 2 fixes applied, …ms, rule downport
Parse, …ms
Iteration 2, 0 fixes applied, …ms, rule downport
Iteration 3, 0 fixes applied, …ms, rule downport
abaplint: 0 issue(s) found, 3 file(s) analyzed
Fixes applied
$ cat src/zcl_atlas_hello.clas.abap
CLASS zcl_atlas_hello DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS greet
IMPORTING iv_name TYPE string
RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.
CLASS zcl_atlas_hello IMPLEMENTATION.
METHOD greet.
DATA lv_name TYPE string.
lv_name = condense( iv_name ).
IF lv_name IS INITIAL.
rv_text = `Hello!`.
ELSE.
rv_text = |Hello, { lv_name }!|.
ENDIF.
ENDMETHOD.
ENDCLASS.
$ npx abap_transpile > transpile.log
$ node output/index.mjs
ZCL_ATLAS_HELLO: running ltcl_greet->greets_by_name
ZCL_ATLAS_HELLO: running ltcl_greet->trims_spaces
ZCL_ATLAS_HELLO: running ltcl_greet->greets_nobody
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 downport_session.py
The first run found two newer constructs and exited with status 1. npx abaplint --fix rewrote both: the class now
declares lv_name with DATA and fills it in a second statement, and the test class creates its object with
CREATE OBJECT instead of NEW. abaplint’s page for the rule asks you to test downported code, because a rewrite
can be wrong, and the last two commands do: all three tests still pass.
Key idea
Match the syntax to the oldest system or tool that must read the code, let downport rewrite what is newer, and prove each rewrite with the tests you already have.
Checkpoint
Check: Syntax levels and downport
The transpiler differs from an SAP system in ways a run can show
The transpiler runs your logic, not your SAP system, and its README lists the differences it knows: sy-sysid is
always ABC, sy-mandt always 123, ABAP SQL is not aware of the client: In an SAP system, a self-contained unit of business data with a three-digit ID such as 100. A user logs on to one client, and ABAP SQL normally reads and writes only that client's rows., fixed point
arithmetic is always on, strings are UCS-2 and the time zone is UTC. A program shows them as well as a test does. A
REPORT writes its output with WRITE into a list the runtime keeps; this four-line wrapper loads the runtime, runs
the program named on its command line and prints that list:
limits/src/zatlas_limits.prog.abap
REPORT zatlas_limits.
DATA lv_i TYPE i.
DATA lv_d34 TYPE decfloat34.
DATA lv_text TYPE string.
WRITE / |sy-sysid: { sy-sysid }|.
WRITE / |sy-mandt: { sy-mandt }|.
WRITE / |sy-zonlo: { sy-zonlo }|.
lv_text = `😀`.
WRITE / |strlen( one emoji ): { strlen( lv_text ) }|.
lv_i = 3 * ( 1 / 3 ).
WRITE / |3 * ( 1 / 3 ) into i: { lv_i }|.
lv_d34 = 1 / 3.
WRITE / |1 / 3 into decfloat34: { lv_d34 }|.
WRITE / |7 / 2 in a template: { 7 / 2 }|.
lv_i = 2147483647.
TRY.
lv_i = lv_i + 1.
WRITE / |2147483647 + 1 into i: { lv_i }|.
CATCH cx_sy_arithmetic_overflow.
WRITE / |2147483647 + 1 into i: CX_SY_ARITHMETIC_OVERFLOW|.
ENDTRY.
AUTHORITY-CHECK OBJECT 'S_TCODE' ID 'TCD' FIELD 'SE38'.
WRITE / |AUTHORITY-CHECK S_TCODE: sy-subrc { sy-subrc }|. limits/run-prog.mjs
import "./output/init.mjs";
await import(`./output/${process.argv[2]}.prog.mjs`);
console.log(abap.console.get()); from session import run
# A REPORT program that prints where the transpiler's results differ from an SAP system's, run through the wrapper
# run-prog.mjs, which loads the runtime, runs the program it is given and prints its WRITE output; then two programs
# that need an SAP system, a screen and a remote function call, which stop with an error.
run("""
cd limits
npx abap_transpile > transpile.log
node run-prog.mjs zatlas_limits
!node run-prog.mjs zatlas_screen
!node run-prog.mjs zatlas_rfc
""") Output
$ cd limits
$ npx abap_transpile > transpile.log
$ node run-prog.mjs zatlas_limits
sy-sysid: ABC
sy-mandt: 123
sy-zonlo: UTC
strlen( one emoji ): 2
3 * ( 1 / 3 ) into i: 1
1 / 3 into decfloat34: 0.3333333333333333
7 / 2 in a template: 3.5000000000000000
2147483647 + 1 into i: 2147483648
AUTHORITY-CHECK S_TCODE: sy-subrc 0
$ node run-prog.mjs zatlas_screen
file://output/zatlas_screen.prog.mjs:4
throw new Error("CallScreen, not supported, transpiler");
^
Error: CallScreen, not supported, transpiler
at file://output/zatlas_screen.prog.mjs:4:7
Node.js v24.21.0
[exit status 1]
$ node run-prog.mjs zatlas_rfc
…/abaplint-2.13.98/node_modules/@abaplint/runtime/build/src/statements/call_function.js:58
throw new Error(`RFC destination ${options.destination} does not exist`);
^
Error: RFC destination PRD does not exist
at CallFunction.callFunction (…/abaplint-2.13.98/node_modules/@abaplint/runtime/build/src/statements/call_function.js:58:23)
at Statements.callFunction (…/abaplint-2.13.98/node_modules/@abaplint/runtime/build/src/statements/index.js:143:63)
at file://output/zatlas_rfc.prog.mjs:4:23
Node.js v24.21.0
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 limits_session.py
Beside each printed line, here is what an SAP system does, from SAP’s own documentation:
| Line of the report | The transpiler printed | An SAP system |
|---|---|---|
sy-sysid, sy-mandt |
ABC and 123 | The system’s own ID and the client you logged on to |
sy-zonlo |
UTC | The time zone of your user |
strlen( one emoji ) |
2 | 2 as well: strlen counts a surrogate pair as two UCS-2 characters |
3 * ( 1 / 3 ) into i |
1 | 0: in calculation type i, every interim result is rounded, and 1 / 3 rounds to 0 |
1 / 3 into decfloat34 |
16 threes | 34 threes: decfloat34 keeps 34 places |
{ 7 / 2 } in a template |
3.5000000000000000 | A calculation of type i, because only the operands count, which rounds 3.5 to 4 |
2147483647 + 1 into i |
2147483648 | The exception CX_SY_ARITHMETIC_OVERFLOW, because the result is outside i |
AUTHORITY-CHECK |
sy-subrc 0 |
0 if your user has the authorisation, otherwise 4 or more |
Some SAP features do not run at all, as the last two commands of the session show: CALL SCREEN 100 stopped with
“CallScreen, not supported, transpiler”, and a remote function call to a destination named PRD stopped with “RFC
destination PRD does not exist”. The later lessons of the module name four more differences where they matter: a
formatting option of string templates, binary searches that miss, CLEANUP blocks and host variables in SQL.
Key idea
Trust the transpiler for the logic of your classes, tables and tests, and check on an SAP system anything this table names before you rely on it.
Checkpoint
Check: Where the transpiler differs from an SAP system
Practise: name the abapGit files of an object
A script that checks a repository, or copies objects from one repository to another, has to know which files belong to which object. The exercise writes that rule as two Python functions, from abapGit’s naming convention, and its sample tests use the files of this lesson’s projects.
Exercise · Easy · Python
Name the abapGit files of an ABAP object
A script that checks an ABAP repository, or one that copies objects between repositories, needs to know which files belong to which object. Write two functions in abapgit_files.py, following abapGit's naming rule: the object's name in lower case, a dot, the object type in lower case, an optional object part, then the extension. A namespace /NAME/ at the start of an object name becomes #name# in the file name.
files_of(object_type, name, test_classes=False) returns the sorted list of file names of one object:
"CLAS":<name>.clas.abapand<name>.clas.xml, plus<name>.clas.testclasses.abapwhentest_classesis true."PROG":<name>.prog.abapand<name>.prog.xml."INTF":<name>.intf.abapand<name>.intf.xml."TABL": only<name>.tabl.xml, because a database table has no source code.- Any other type raises
ValueError.
object_of(file_name) goes the other way and returns a tuple (object_type, object_name) in upper case, with #name# turned back into /NAME/. It returns None for a file that belongs to no object of those four types, such as package.json, abap_transpile.json or package.devc.xml, whose name is a fixed word rather than an object name, and for a name without all three pieces, such as zcl_atlas_hello.clas or .abapgit.xml.
files_of("CLAS", "ZCL_ATLAS_HELLO", True)is["zcl_atlas_hello.clas.abap", "zcl_atlas_hello.clas.testclasses.abap", "zcl_atlas_hello.clas.xml"].files_of("PROG", "/FOOBAR/REPORT")is["#foobar#report.prog.abap", "#foobar#report.prog.xml"].object_of("zcl_atlas_hello.clas.testclasses.abap")is("CLAS", "ZCL_ATLAS_HELLO").
The sample tests use the files of this lesson's projects.
Starter code · abapgit_files.py
def files_of(object_type, name, test_classes=False):
"""Return the sorted file names abapGit uses for one ABAP object, as the prompt describes."""
# Replace this line with your code.
return []
def object_of(file_name):
"""Return (object_type, object_name) in upper case for an object's file, or None for any other file."""
# Replace this line with your code.
return None The sample tests · test_abapgit_files.py
from abapgit_files import files_of, object_of
def test_class_with_its_test_classes():
"""names the three files of the hello class"""
assert files_of("CLAS", "ZCL_ATLAS_HELLO", True) == [
"zcl_atlas_hello.clas.abap",
"zcl_atlas_hello.clas.testclasses.abap",
"zcl_atlas_hello.clas.xml",
]
def test_class_without_test_classes():
"""leaves out the test include when the class has no tests"""
assert files_of("CLAS", "ZCX_INSUFFICIENT_FUNDS") == ["zcx_insufficient_funds.clas.abap", "zcx_insufficient_funds.clas.xml"]
def test_program_and_table():
"""names the two files of a program and the one file of a table"""
assert files_of("PROG", "ZATLAS_LIMITS") == ["zatlas_limits.prog.abap", "zatlas_limits.prog.xml"]
assert files_of("TABL", "ZATLAS_ORDER") == ["zatlas_order.tabl.xml"]
def test_namespace():
"""turns /FOOBAR/ into #foobar#"""
assert files_of("PROG", "/FOOBAR/REPORT") == ["#foobar#report.prog.abap", "#foobar#report.prog.xml"]
def test_object_of_a_test_include():
"""finds the class a test include belongs to"""
assert object_of("zcl_atlas_hello.clas.testclasses.abap") == ("CLAS", "ZCL_ATLAS_HELLO")
assert object_of("zatlas_limits.prog.xml") == ("PROG", "ZATLAS_LIMITS")
def test_object_of_a_namespace():
"""turns #foobar# back into /FOOBAR/"""
assert object_of("#foobar#report.prog.abap") == ("PROG", "/FOOBAR/REPORT")
def test_files_of_no_object():
"""returns None for the project's other files"""
assert object_of("package.json") is None
assert object_of("abap_transpile.json") is None
assert object_of("package.devc.xml") is None
def test_names_that_are_not_object_files():
"""returns None for a name without an extension and for the repository's settings file"""
assert object_of("zcl_atlas_hello.clas") is None
assert object_of(".abapgit.xml") is None
def test_unknown_type():
"""refuses a type it does not know"""
try:
files_of("FUGR", "ZATLAS_GROUP")
except ValueError:
return
assert False, "files_of should raise ValueError for an unknown type such as FUGR" A hint
For object_of, split the file name at its dots: the first piece is the object's name and the second its type, and anything after them is the object part and the extension. name.upper() and str.replace do the case and the # characters; check the type against the four you know before you build the tuple.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then check yourself on the six ideas, from where ABAP runs to what the transpiler cannot show you; your own answers decide the order, shakiest idea first:
Practice that adapts to you
Practise this lesson
18 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to mid-level): What does the abaplint transpiler do, and what can it not do for an ABAP team?
It turns ABAP source files into JavaScript that Node.js runs, together with open-abap-core, an MIT-licensed library of
standard classes such as cl_abap_unit_assert. A team can therefore run its ABAP Unit tests on any computer or build
server, on every push, without logging on to an SAP system, as abapGit and abap2UI5 do. It runs the logic of classes,
internal tables, exceptions and tests. It does not give you the SAP system: screens and SAP GUI, remote function
calls, real database tables with their data, users and authorisations. Some results also differ, because the
transpiler is a different runtime: sy-sysid is always ABC, SQL ignores clients, interim results of integer
arithmetic are not rounded, and an AUTHORITY-CHECK always passes. So the transpiler is a fast first check, and
anything that touches those parts is tested again on an SAP system.
Two harder questions follow, about running ABAP tests away from the SAP system and about results that disagree:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
Your team wants its ABAP Unit tests to run on every pull request, without anyone logging on to an SAP system. How would you set that up, and what would you still test on the SAP system?
-
Mid to staff
A unit test passes on the transpiler and fails on your SAP development system with the same code. How do you find out which result is right, and what do you change?
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How does ABAP code run on a laptop without an SAP system?
The abaplint transpiler writes JavaScript from the ABAP files, Node.js runs it with @abaplint/runtime, and open-abap-core, an ABAP library of standard classes such as cl_abap_unit_assert, is transpiled with your code.
-
Why does the ABAP setup read open-abap-core from a local clone at one commit, not from a url?
Given a url, the transpiler clones the newest commit on every run, so a result could change with no change of yours. A pinned local folder keeps every run repeatable.
-
Which files does abapGit keep for the class ZCL_ATLAS_HELLO?
zcl_atlas_hello.clas.abap for the source, zcl_atlas_hello.clas.testclasses.abap for its local test classes, and zcl_atlas_hello.clas.xml for what the system keeps beside the code, such as the description.
-
The ABAP Unit run printed a running line for a test. Did that test pass?
Not necessarily: the line is printed before the test starts. The runner stops at the first failure, so later tests never run, and only exit status 0 says that all of them passed.
-
What does abaplint's downport rule do, and when is it active?
It rewrites newer syntax, such as NEW and inline DATA( ), as ABAP 7.02 statements. It works only when abaplint.json sets the syntax version v702, and the existing tests then prove the rewrite.
-
What do sy-sysid, sy-mandt and an AUTHORITY-CHECK give on the transpiler?
Always ABC and 123, and a check that passes with sy-subrc 0. An SAP system gives its own ID, the client you logged on to, and 4 or more when your user lacks the authorisation.
-
3 * ( 1 / 3 ) is assigned to a variable of type i. What do the transpiler and an SAP system store?
1 on the transpiler, 0 on an SAP system: in calculation type i, SAP rounds every interim result, so 1 / 3 is already 0.
-
What breaks when an abap_transpile.json has "options": {} instead of keeping addCommonJS?
Transpiling looks fine, but running the tests fails at once with ReferenceError: cl_abap_objectdescr is not defined: without addCommonJS the class modules do not import the classes they depend on.
Key takeaways
- ABAP runs inside SAP’s application servers, and the abaplint transpiler runs a large part of it on Node.js, with open-abap-core supplying the standard classes.
- A reproducible setup pins Node.js 24, three exact npm versions with a lockfile and one commit of open-abap-core, and every run starts by listing those versions.
- abapGit stores an object as files named after the object and its type, so a class is its source, its test classes and an XML descriptor that abaplint, the transpiler and an SAP system all read.
- A failing ABAP Unit run names the test, prints the actual and expected values and exits with status 1, and the tests after the first failure do not run.
- abaplint’s downport rule rewrites 7.40 syntax as 7.02, and the tests prove the rewrite.
- The transpiler’s results differ from an SAP system’s in measurable ways, such as unrounded interim results, a
16-digit
decfloat34and authorisation checks that always pass, so those cases are checked on an SAP system.
Terms in this lesson
- ABAP Unit
- ABAP's built-in unit test framework: local test classes declared FOR TESTING, whose test methods check results with the methods of the class cl_abap_unit_assert, such as assert_equals.
- abapGit
- A Git client for ABAP, written in ABAP and run inside an SAP system. It stores each ABAP object in a repository as files named after the object and its type, such as zcl_atlas_hello.clas.abap.
- client
- In an SAP system, a self-contained unit of business data with a three-digit ID such as 100. A user logs on to one client, and ABAP SQL normally reads and writes only that client's rows.
- downport
- Rewriting ABAP written in newer syntax as equivalent older syntax, such as 7.02, so that older systems and tools accept it. abaplint's downport rule does it for you when the target version is v702.
References
- ABAP Keyword Documentation, release 7.57: overview (SAP)
- ABAP release news: ABAP releases and SAP S/4HANA releases (SAP)
- abaplint transpiler: ABAP to JavaScript transpiler and runtime (README) (abaplint)
- @abaplint/transpiler-cli (versions and licence) (npm)
- open-abap-core: reusable ABAP functionality for the transpiler (open-abap)
- abapGit documentation: welcome to abapGit (abapGit)
- abapGit documentation: folders and files (abapGit)
- abapGit documentation: namespaces (abapGit)
- abaplint rules: downport (abaplint)
- npm ci (npm)
- CLASS, FOR TESTING (SAP)
- arith_exp: calculation type and calculation rules (SAP)
- ABAP cheat sheet: numeric operations (SAP)
- Built-in numeric types (SAP)
- Length functions (strlen and surrogate pairs) (SAP)
- ABAP SQL: client handling (SAP)
- AUTHORITY-CHECK (SAP)
- Release news 7.0 EhP2: character string processing (string templates) (SAP)
- Release news 7.0 EhP2: decimal floating point numbers (SAP)
- Release news 7.40 SP02: expressions and functions (SAP)
- Release news 7.40 SP05: ABAP SQL (escape character for host variables) (SAP)
- Release news 7.50: data types (int8) (SAP)
- Release news 7.50: only Unicode systems (SAP)
- Exercism: the ABAP track (Exercism)
- Exercism ABAP test runner (README and package.json) (Exercism)
Report a problem with this lesson
Kept only in this browser. Your Learn progress