diff --git a/.github/workflows/check-atc.yaml b/.github/workflows/check-atc.yaml new file mode 100644 index 00000000..2bc76dad --- /dev/null +++ b/.github/workflows/check-atc.yaml @@ -0,0 +1,46 @@ +name: check-atc + +# The extended program check (SLIN / ATC) runs in the systems these samples are +# INSTALLED on and nowhere here - abaplint models none of its findings. So a +# sample passes every other gate in this repository and the problem surfaces on +# somebody's system, which is worse than a missing sample: it is the code they +# copied because it was published as the way to do the thing. +# +# Three findings, each named by the rule that decides it: +# +# nowhere 21 SELECT statements read a small demo table with no +# WHERE and none of them said so +# subrc_after_assign 14 sy-subrc tests sat after a dynamic ASSIGN, among them +# the sub-app view handover and a node builder inside a DO +# text_symbol_arg three text symbols went straight to the view builder's +# `v`, which is TYPE string - a SYNTAX_ERROR of the class, +# reported from a system on 2026-09-16 +# +# A JOB of its own, not a step of `npm run check`: this repository runs one +# workflow per gate, because a gate that only exists in the local chain cannot +# turn a pull request red - the same reason check-abapdoc has one. +# +# Plain node, no dependencies, so it stays a few seconds. + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: check-atc-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-atc: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '22' + - run: node scripts/check-atc.mjs diff --git a/package.json b/package.json index 98ea792e..4cc09573 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,12 @@ "check:prose": "node scripts/check-prose-names.mjs", "check:docs-links": "node scripts/check-docs-links.mjs", "check:app-rules": "node scripts/check-app-rules.mjs", + "check:atc": "node scripts/check-atc.mjs", "check:pin": "node scripts/check-framework-pin.mjs", "rename": "abaplint .github/abaplint/rename_test.jsonc --rename", "syfixes": "find . -type f -name '*.abap' -exec sed -i -e 's/ RAISE EXCEPTION TYPE cx_sy_itab_line_not_found/ ASSERT 1 = 0/g' {} + ", "downport": "rm -rf src/00 && abaplint --fix .github/abaplint/abap_702.jsonc && npm run syfixes && node scripts/generate-samples-md.mjs", - "check": "npm run check:pin && npm run lint && npm run check:cloud && npm run check:abap2ui5 && npm run check:agents && npm run check:strip && npm run check:orphans && npm run check:keywords && npm run check:launchpad && npm run check:catalogue && npm run check:derived && npm run check:prose && npm run check:docs-links && npm run check:app-rules && npm run rename" + "check": "npm run check:pin && npm run lint && npm run check:cloud && npm run check:abap2ui5 && npm run check:agents && npm run check:strip && npm run check:orphans && npm run check:keywords && npm run check:launchpad && npm run check:catalogue && npm run check:derived && npm run check:prose && npm run check:docs-links && npm run check:app-rules && npm run check:atc && npm run rename" }, "repository": { "type": "git", diff --git a/scripts/check-atc.mjs b/scripts/check-atc.mjs new file mode 100644 index 00000000..83d5bf53 --- /dev/null +++ b/scripts/check-atc.mjs @@ -0,0 +1,177 @@ +#!/usr/bin/env node +/* + * check-atc — two findings a sample can ship that no gate here could see. + * + * Both come from the same place: abaplint models neither, so a sample passes + * every gate in this repository and the problem surfaces on the system + * somebody installed it on. That is worse than a missing sample - it is the + * code they copied because it was published as the way to do the thing. + * + * 1. NOWHERE - a `SELECT` with no `WHERE` clause. + * + * The extended program check (SLIN / ATC) runs in those systems and + * nowhere here; it wants the pseudo-comment `"#EC CI_NOWHERE` on the + * statement. The framework's own z2ui5_cl_ui5_srv_draft=>count_entries_total + * is the precedent - it reads the whole draft table on purpose and says so. + * Twenty-one statements here read a small demo table with no WHERE, which is + * exactly what they demonstrate; none said so until 2026-09-16. + * + * 2. SUBRC_AFTER_ASSIGN - `sy-subrc` read after a plain dynamic `ASSIGN`. + * + * On some releases a SUCCESSFUL assign does not reset `sy-subrc`, so the + * test reads FALSE for an assignment that worked and TRUE for one that did + * not (abap2UI5 #1937). `IS [NOT] ASSIGNED` is the check that holds on all + * of them - and inside a LOOP, `UNASSIGN .` has to come FIRST, because + * a failed assign leaves the previous round's binding in place and + * `IS ASSIGNED` would then read TRUE for the failure. Fourteen of these + * shipped here, among them the sub-app view handover (`MV_VIEW_DISPLAY`, + * `VIEW_PARENT`) - and app_502's node builder, which assigns inside a + * `DO`, where a stale binding builds onto the wrong node. + * + * `ASSIGN COMPONENT … OF STRUCTURE` is the NEGATIVE and must never be + * reported: there `sy-subrc` distinguishes "component not found" and IS + * the documented check. Reporting it is how a cleanup turns a + * wrong-branch bug into a silently-taken one. + * + * 3. TEXT_SYMBOL_ARG - a text symbol (`'text'(001)`) handed to a PARAMETER. + * + * It is a CHARACTER literal, so a formal parameter typed `string` - the + * view builder's `v`, for one - answers `'...'(001) is not type-compatible + * with formal parameter V`, a SYNTAX_ERROR of the whole class. Three of these shipped here, in app 519 - the sample whose subject IS + * translatable texts, so the one place it was certain to appear. + * + * A PARAMETER binding only: `lv_x = 'y'(001).` is an assignment and a plain + * conversion, and a symbol inside a string template sits in a general + * expression position. Read it into a variable and pass that. + * + * The scan reads ABAP STATEMENTS, not lines - a multi-line `ASSIGN COMPONENT` + * looks like a plain `ASSIGN` to a line-based scan, which would report the one + * shape that must not be reported. + * + * Run: npm run check:atc + */ +import { readdirSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const ROOT = new URL('..', import.meta.url).pathname; + +function abapFiles(dir, out = []) { + for (const entry of readdirSync(join(ROOT, dir), { withFileTypes: true })) { + const rel = `${dir}/${entry.name}`; + if (entry.isDirectory()) abapFiles(rel, out); + else if (rel.endsWith('.abap')) out.push(rel); + } + return out; +} + +/* The code half of a line: the trailing `"` comment cut off, outside string + * literals so a `"` in a `'…'` or `|…|` stays text. */ +function code(line) { + let quote = null; + for (let i = 0; i < line.length; i++) { + const ch = line[i]; + if (quote) { + if (ch === quote) quote = null; + continue; + } + if (ch === "'" || ch === '|') quote = ch; + else if (ch === '"') return line.slice(0, i); + } + return line; +} + +/* Statements, with the line they start on and their raw text (the raw half + * keeps the pseudo-comments rule 1 decides on). */ +function statements(source) { + const out = []; + let raw = []; + let start = 0; + source.split(/\r?\n/).forEach((line, i) => { + if (/^\s*\*/.test(line)) return; + const c = code(line); + if (raw.length === 0) { + if (c.trim() === '') return; + start = i + 1; + } + raw.push(line); + if (c.trimEnd().endsWith('.') || c.trimEnd().endsWith(':')) { + out.push({ start, raw: raw.join('\n'), code: raw.map(code).join(' ').replace(/\s+/g, ' ').trim() }); + raw = []; + } + }); + if (raw.length > 0) out.push({ start, raw: raw.join('\n'), code: raw.map(code).join(' ').replace(/\s+/g, ' ').trim() }); + return out; +} + +/* Statements that write sy-subrc and so end an ASSIGN's claim on it. + * Deliberately short and deliberately over-broad: one missing from the list + * makes the scan report MORE, which a reader catches, rather than less. */ +const SETS_SUBRC = + /^(read\s+table|select|loop\s+at|find|replace|call\s+function|call\s+method|delete|insert|modify|append|split|open\s+dataset|authority-check|import|export|describe|search|get\s+parameter|set\s+parameter)\b/i; + +const TEXT_SYMBOL_ARG = /\b\w+\s*=\s*'[^']*'\(\d{3}\)/; + +const findings = []; + +for (const rel of abapFiles('src')) { + let claim = null; + for (const st of statements(readFileSync(join(ROOT, rel), 'utf8'))) { + const { code: c, raw, start } = st; + + if (/^SELECT\b/i.test(c) && !/\bWHERE\b/i.test(c) && !/#EC\s+CI_NOWHERE/i.test(raw)) { + findings.push({ + rule: 'nowhere', + at: `${rel}:${start}`, + message: 'SELECT without a WHERE clause - the extended check wants "#EC CI_NOWHERE on the statement', + }); + } + + const symbolAt = c.search(TEXT_SYMBOL_ARG); + if (symbolAt !== -1) { + const head = c.slice(0, symbolAt); + const depth = (head.match(/\(/g) ?? []).length - (head.match(/\)/g) ?? []).length; + if (depth > 0) { + findings.push({ + rule: 'text_symbol_arg', + at: `${rel}:${start}`, + message: "a text symbol is a CHARACTER literal - a parameter typed `string` answers \"not type-compatible with formal parameter\"; read it into a variable and pass that", + }); + } + } + + if (/^ASSIGN\b/i.test(c)) { + claim = /^ASSIGN\s+COMPONENT\b/i.test(c) ? 'component' : 'plain'; + continue; + } + if (/\bsy-subrc\b/i.test(c)) { + if (claim === 'plain') { + findings.push({ + rule: 'subrc_after_assign', + at: `${rel}:${start}`, + message: `${c.slice(0, 70)} - a successful dynamic ASSIGN does not reset sy-subrc on every release (#1937); use IS [NOT] ASSIGNED`, + }); + } + claim = null; + continue; + } + if (SETS_SUBRC.test(c) || /\bEXCEPTIONS\b/i.test(c)) claim = null; + if (/^(METHOD|ENDMETHOD|FORM|ENDFORM)\b/i.test(c)) claim = null; + } +} + +if (findings.length > 0) { + console.error('check-atc: these fire on the system a sample is installed on.\n'); + for (const f of findings) console.error(` [${f.rule}] ${f.at}\n ${f.message}`); + console.error( + '\nnowhere - a full read on purpose says so, on the first line of the\n' + + ' statement; see any annotated SELECT under src/.\n' + + 'text_symbol_arg - read the symbol into a variable and pass the variable;\n' + + ' an assignment and a string template need nothing.\n' + + 'subrc_after_assign - IS [NOT] ASSIGNED, and `UNASSIGN .` BEFORE the ASSIGN\n' + + ' when it sits in a loop or the symbol was assigned earlier.\n' + + ' ASSIGN COMPONENT is not this finding and is never reported.', + ); + process.exit(1); +} + +console.log('check-atc: no SELECT without a WHERE, no sy-subrc after a dynamic ASSIGN,\n no text symbol passed to a parameter - OK'); diff --git a/src/00/98/z2ui5_cl_smp_app_117.clas.abap b/src/00/98/z2ui5_cl_smp_app_117.clas.abap index 003be0f9..dcaf4efb 100644 --- a/src/00/98/z2ui5_cl_smp_app_117.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_117.clas.abap @@ -145,9 +145,11 @@ CLASS z2ui5_cl_smp_app_117 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_126.clas.abap b/src/00/98/z2ui5_cl_smp_app_126.clas.abap index d9f9e546..d800fb43 100644 --- a/src/00/98/z2ui5_cl_smp_app_126.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_126.clas.abap @@ -99,7 +99,7 @@ CLASS z2ui5_cl_smp_app_126 IMPLEMENTATION. ASSIGN mt_table->* TO . - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
UP TO 3 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_131.clas.abap b/src/00/98/z2ui5_cl_smp_app_131.clas.abap index 1c333807..562af91f 100644 --- a/src/00/98/z2ui5_cl_smp_app_131.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_131.clas.abap @@ -148,9 +148,11 @@ CLASS z2ui5_cl_smp_app_131 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_184.clas.abap b/src/00/98/z2ui5_cl_smp_app_184.clas.abap index f8320c30..085fb723 100644 --- a/src/00/98/z2ui5_cl_smp_app_184.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_184.clas.abap @@ -149,7 +149,7 @@ CLASS z2ui5_cl_smp_app_184 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_185.clas.abap b/src/00/98/z2ui5_cl_smp_app_185.clas.abap index 41fb2980..8fcb9440 100644 --- a/src/00/98/z2ui5_cl_smp_app_185.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_185.clas.abap @@ -132,9 +132,11 @@ CLASS z2ui5_cl_smp_app_185 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_190.clas.abap b/src/00/98/z2ui5_cl_smp_app_190.clas.abap index 68c3d188..fa10a0f0 100644 --- a/src/00/98/z2ui5_cl_smp_app_190.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_190.clas.abap @@ -159,7 +159,7 @@ CLASS z2ui5_cl_smp_app_190 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_191.clas.abap b/src/00/98/z2ui5_cl_smp_app_191.clas.abap index 95addf5d..8231f755 100644 --- a/src/00/98/z2ui5_cl_smp_app_191.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_191.clas.abap @@ -133,9 +133,11 @@ CLASS z2ui5_cl_smp_app_191 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_194.clas.abap b/src/00/98/z2ui5_cl_smp_app_194.clas.abap index a27e230a..79312dcc 100644 --- a/src/00/98/z2ui5_cl_smp_app_194.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_194.clas.abap @@ -197,7 +197,7 @@ CLASS z2ui5_cl_smp_app_194 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_195.clas.abap b/src/00/98/z2ui5_cl_smp_app_195.clas.abap index 86532ac5..a9ad077d 100644 --- a/src/00/98/z2ui5_cl_smp_app_195.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_195.clas.abap @@ -132,9 +132,11 @@ CLASS z2ui5_cl_smp_app_195 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_199.clas.abap b/src/00/98/z2ui5_cl_smp_app_199.clas.abap index 1f1269f5..f2c1f41f 100644 --- a/src/00/98/z2ui5_cl_smp_app_199.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_199.clas.abap @@ -136,7 +136,7 @@ CLASS z2ui5_cl_smp_app_199 IMPLEMENTATION. CAST cl_abap_tabledescr( cl_abap_typedescr=>describe_by_data(
) )->get_table_line_type( ) )->get_components( ). - SELECT id, id_prev FROM z2ui5_t_01 + SELECT id, id_prev FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
UP TO 2 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_211.clas.abap b/src/00/98/z2ui5_cl_smp_app_211.clas.abap index bbc7581f..58db00e0 100644 --- a/src/00/98/z2ui5_cl_smp_app_211.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_211.clas.abap @@ -159,9 +159,11 @@ CLASS z2ui5_cl_smp_app_211 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO FIELD-SYMBOL(). - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_212.clas.abap b/src/00/98/z2ui5_cl_smp_app_212.clas.abap index c568c9a7..757ad80e 100644 --- a/src/00/98/z2ui5_cl_smp_app_212.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_212.clas.abap @@ -82,11 +82,25 @@ CLASS z2ui5_cl_smp_app_212 IMPLEMENTATION. FIELD-SYMBOLS TYPE STANDARD TABLE. FIELD-SYMBOLS TYPE any. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937). Both references are also + " checked before they are USED: reading [ ] or a component of an + " UNASSIGNED field symbol is a short dump, not an exception - and the + " popup would take the whole roundtrip down with it ASSIGN mt_table->* TO . + IF IS NOT ASSIGNED. + RETURN. + ENDIF. + + " the row structure does not change per field - read once, before the loop + ASSIGN ms_table_row->* TO . + IF IS NOT ASSIGNED. + RETURN. + ENDIF. ASSIGN [ index ] TO FIELD-SYMBOL(). - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. @@ -98,7 +112,6 @@ CLASS z2ui5_cl_smp_app_212 IMPLEMENTATION. CONTINUE. ENDIF. - ASSIGN ms_table_row->* TO . ASSIGN COMPONENT lv_field OF STRUCTURE TO FIELD-SYMBOL(). IF sy-subrc <> 0. @@ -286,7 +299,7 @@ CLASS z2ui5_cl_smp_app_212 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_328.clas.abap b/src/00/98/z2ui5_cl_smp_app_328.clas.abap index 977190eb..c17249bb 100644 --- a/src/00/98/z2ui5_cl_smp_app_328.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_328.clas.abap @@ -148,7 +148,7 @@ CLASS z2ui5_cl_smp_app_328 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT id FROM z2ui5_t_01 + SELECT id FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
UP TO 4 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_331.clas.abap b/src/00/98/z2ui5_cl_smp_app_331.clas.abap index d407803a..8984d689 100644 --- a/src/00/98/z2ui5_cl_smp_app_331.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_331.clas.abap @@ -88,7 +88,7 @@ CLASS z2ui5_cl_smp_app_331 IMPLEMENTATION. " any single row will do here, but it has to be the SAME one on every " roundtrip - SELECT SINGLE without a full key leaves that to the database - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @DATA(lt_data) UP TO 1 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_332.clas.abap b/src/00/98/z2ui5_cl_smp_app_332.clas.abap index 1d521c12..d8f67e0c 100644 --- a/src/00/98/z2ui5_cl_smp_app_332.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_332.clas.abap @@ -102,7 +102,7 @@ CLASS z2ui5_cl_smp_app_332 IMPLEMENTATION. " any single row will do here, but it has to be the SAME one on every " roundtrip - SELECT SINGLE without a full key leaves that to the database - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @DATA(lt_data) UP TO 1 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_334.clas.abap b/src/00/98/z2ui5_cl_smp_app_334.clas.abap index 80741561..86ceba95 100644 --- a/src/00/98/z2ui5_cl_smp_app_334.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_334.clas.abap @@ -117,7 +117,7 @@ CLASS z2ui5_cl_smp_app_334 IMPLEMENTATION. " any single row will do here, but it has to be the SAME one on every " roundtrip - SELECT SINGLE without a full key leaves that to the database - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @DATA(lt_data) UP TO 1 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_335.clas.abap b/src/00/98/z2ui5_cl_smp_app_335.clas.abap index c3b56050..1f3a741b 100644 --- a/src/00/98/z2ui5_cl_smp_app_335.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_335.clas.abap @@ -147,7 +147,7 @@ CLASS z2ui5_cl_smp_app_335 IMPLEMENTATION. " any single row will do here, but it has to be the SAME one on every " roundtrip - SELECT SINGLE without a full key leaves that to the database - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @DATA(lt_data) UP TO 1 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_337.clas.abap b/src/00/98/z2ui5_cl_smp_app_337.clas.abap index f0b6e2ea..f5543da2 100644 --- a/src/00/98/z2ui5_cl_smp_app_337.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_337.clas.abap @@ -147,7 +147,7 @@ CLASS z2ui5_cl_smp_app_337 IMPLEMENTATION. METHOD get_data. - SELECT id, + SELECT id, "#EC CI_NOWHERE id_prev, id_prev_app, id_prev_app_stack, diff --git a/src/00/98/z2ui5_cl_smp_app_338.clas.abap b/src/00/98/z2ui5_cl_smp_app_338.clas.abap index 780f8bc3..b421f5db 100644 --- a/src/00/98/z2ui5_cl_smp_app_338.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_338.clas.abap @@ -147,9 +147,11 @@ CLASS z2ui5_cl_smp_app_338 IMPLEMENTATION. RETURN. ENDTRY. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN mo_app->(`MV_VIEW_DISPLAY`) TO . - IF sy-subrc = 0 AND = abap_true. + IF IS ASSIGNED AND = abap_true. = abap_false. client->view_display( mo_main_page->stringify( ) ). diff --git a/src/00/98/z2ui5_cl_smp_app_339.clas.abap b/src/00/98/z2ui5_cl_smp_app_339.clas.abap index 7a0405d4..a4811bfc 100644 --- a/src/00/98/z2ui5_cl_smp_app_339.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_339.clas.abap @@ -219,7 +219,7 @@ CLASS z2ui5_cl_smp_app_339 IMPLEMENTATION. ASSIGN mt_table->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_342.clas.abap b/src/00/98/z2ui5_cl_smp_app_342.clas.abap index 8097157f..eab62e3a 100644 --- a/src/00/98/z2ui5_cl_smp_app_342.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_342.clas.abap @@ -223,7 +223,7 @@ CLASS z2ui5_cl_smp_app_342 IMPLEMENTATION. ASSIGN mt_data->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (mv_table) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_343.clas.abap b/src/00/98/z2ui5_cl_smp_app_343.clas.abap index 3672da6b..c6040bb3 100644 --- a/src/00/98/z2ui5_cl_smp_app_343.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_343.clas.abap @@ -66,7 +66,7 @@ CLASS Z2UI5_CL_SMP_APP_343 IMPLEMENTATION. CREATE DATA mt_data1 TYPE HANDLE new_table_desc. ASSIGN mt_data1->* TO . - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @ UP TO 5 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_344.clas.abap b/src/00/98/z2ui5_cl_smp_app_344.clas.abap index c1866fed..06d663cb 100644 --- a/src/00/98/z2ui5_cl_smp_app_344.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_344.clas.abap @@ -176,7 +176,7 @@ CLASS z2ui5_cl_smp_app_344 IMPLEMENTATION. ASSIGN mt_data->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (iv_tabname) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
@@ -207,7 +207,7 @@ CLASS z2ui5_cl_smp_app_344 IMPLEMENTATION. ASSIGN mt_data2->* TO
. - SELECT * + SELECT * "#EC CI_NOWHERE FROM (iv_tabname) ORDER BY PRIMARY KEY INTO CORRESPONDING FIELDS OF TABLE @
diff --git a/src/00/98/z2ui5_cl_smp_app_345.clas.abap b/src/00/98/z2ui5_cl_smp_app_345.clas.abap index f40a4cdc..dd624a61 100644 --- a/src/00/98/z2ui5_cl_smp_app_345.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_345.clas.abap @@ -75,7 +75,7 @@ CLASS Z2UI5_CL_SMP_APP_345 IMPLEMENTATION. CREATE DATA mt_data1 TYPE HANDLE new_table_desc. ASSIGN mt_data1->* TO . - SELECT * FROM z2ui5_t_01 + SELECT * FROM z2ui5_t_01 "#EC CI_NOWHERE ORDER BY PRIMARY KEY INTO TABLE @ UP TO 5 ROWS. diff --git a/src/00/98/z2ui5_cl_smp_app_347.clas.abap b/src/00/98/z2ui5_cl_smp_app_347.clas.abap index fe9a81f0..138a6798 100644 --- a/src/00/98/z2ui5_cl_smp_app_347.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_347.clas.abap @@ -132,7 +132,7 @@ CLASS z2ui5_cl_smp_app_347 IMPLEMENTATION. METHOD get_data. - SELECT id, + SELECT id, "#EC CI_NOWHERE id_prev, id_prev_app, id_prev_app_stack, diff --git a/src/00/98/z2ui5_cl_smp_app_348.clas.abap b/src/00/98/z2ui5_cl_smp_app_348.clas.abap index e93dc07b..5e1ba8b8 100644 --- a/src/00/98/z2ui5_cl_smp_app_348.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_348.clas.abap @@ -116,7 +116,7 @@ CLASS z2ui5_cl_smp_app_348 IMPLEMENTATION. " any single row will do here, but it has to be the SAME one on every " roundtrip - SELECT SINGLE without a full key leaves that to the database - SELECT id, + SELECT id, "#EC CI_NOWHERE id_prev, id_prev_app, id_prev_app_stack, diff --git a/src/00/98/z2ui5_cl_smp_app_349.clas.abap b/src/00/98/z2ui5_cl_smp_app_349.clas.abap index ffc38e07..59d243e5 100644 --- a/src/00/98/z2ui5_cl_smp_app_349.clas.abap +++ b/src/00/98/z2ui5_cl_smp_app_349.clas.abap @@ -155,7 +155,7 @@ CLASS z2ui5_cl_smp_app_349 IMPLEMENTATION. METHOD get_data. - SELECT id, + SELECT id, "#EC CI_NOWHERE id_prev, id_prev_app, id_prev_app_stack, diff --git a/src/01/z2ui5_cl_smp_app_104.clas.abap b/src/01/z2ui5_cl_smp_app_104.clas.abap index 0e02ee35..9338c09e 100644 --- a/src/01/z2ui5_cl_smp_app_104.clas.abap +++ b/src/01/z2ui5_cl_smp_app_104.clas.abap @@ -44,9 +44,11 @@ CLASS z2ui5_cl_smp_app_104 IMPLEMENTATION. IF app_sub IS BOUND. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN app_sub->(`VIEW_PARENT`) TO FIELD-SYMBOL(). - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. @@ -65,7 +67,7 @@ CLASS z2ui5_cl_smp_app_104 IMPLEMENTATION. ASSIGN app_sub->(`VIEW_PARENT`) TO FIELD-SYMBOL(). - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. diff --git a/src/01/z2ui5_cl_smp_app_461.clas.abap b/src/01/z2ui5_cl_smp_app_461.clas.abap index 9f8b87e9..ea31db76 100644 --- a/src/01/z2ui5_cl_smp_app_461.clas.abap +++ b/src/01/z2ui5_cl_smp_app_461.clas.abap @@ -81,12 +81,14 @@ CLASS z2ui5_cl_smp_app_461 IMPLEMENTATION. IF lv_from_root = lv_to_root. RETURN. ENDIF. + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN t_nodes[ lv_from_root ] TO FIELD-SYMBOL(). - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. ASSIGN t_nodes[ lv_to_root ] TO FIELD-SYMBOL(). - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. DELETE -nodes INDEX lv_from_child. diff --git a/src/01/z2ui5_cl_smp_app_502.clas.abap b/src/01/z2ui5_cl_smp_app_502.clas.abap index 4cdac6cb..4c5f081f 100644 --- a/src/01/z2ui5_cl_smp_app_502.clas.abap +++ b/src/01/z2ui5_cl_smp_app_502.clas.abap @@ -305,8 +305,10 @@ CLASS z2ui5_cl_smp_app_502 IMPLEMENTATION. " stops at five and writes an ellipsis where it stopped - without the " limit a structure that points at itself would never end DATA(tree) = get_tree( ). + " IS ASSIGNED, not sy-subrc: a SUCCESSFUL dynamic ASSIGN does not reset + " sy-subrc on every release (abap2UI5 #1937) ASSIGN tree->* TO . - IF sy-subrc = 0. + IF IS ASSIGNED. client->message_box_display( ). ENDIF. @@ -701,8 +703,12 @@ CLASS z2ui5_cl_smp_app_502 IMPLEMENTATION. " built from the bottom up, so every node holds the one below it DO 6 TIMES. CREATE DATA result TYPE ty_s_node. + " UNASSIGN before the ASSIGN because this is a loop: a failed assign + " leaves the previous round's binding in place, and IS ASSIGNED would + " then read TRUE for the failure and build onto the WRONG node + UNASSIGN . ASSIGN result->* TO . - IF sy-subrc <> 0. + IF IS NOT ASSIGNED. RETURN. ENDIF. diff --git a/src/01/z2ui5_cl_smp_app_519.clas.abap b/src/01/z2ui5_cl_smp_app_519.clas.abap index d8ab4c71..c34af6f8 100644 --- a/src/01/z2ui5_cl_smp_app_519.clas.abap +++ b/src/01/z2ui5_cl_smp_app_519.clas.abap @@ -46,6 +46,11 @@ CLASS z2ui5_cl_smp_app_519 IMPLEMENTATION. METHOD view_display. + " the three text symbols this app is about - see the block below + DATA lv_name_label TYPE string. + DATA lv_placeholder TYPE string. + DATA lv_greet TYPE string. + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( )->ele( n = `View` ns = `mvc` )->a( n = `displayBlock` v = `true` @@ -70,22 +75,35 @@ CLASS z2ui5_cl_smp_app_519 IMPLEMENTATION. " Every text below comes out of the class's text pool (Goto > Text " Elements in SE24/ADT). The literal in the source is the fallback and the " maintenance text; what renders is the entry for the logon language. + " + " Read into a VARIABLE first, and that is part of the lesson: a text + " symbol is a CHARACTER literal, while the builder's v is TYPE string. + " Handing one straight to v answers `'...'(001) is not type-compatible + " with formal parameter V` on a system - a SYNTAX_ERROR of the class, + " although abaplint, the transpiler and the unit suite are all green on + " it. The assignment below is a plain conversion and is allowed on every + " release. Inside a string template ( see on_event( ) ) the symbol needs + " no variable: an embedded expression is a general expression position. + lv_name_label = 'Your name'(001). + lv_placeholder = 'Type a name here'(002). + lv_greet = 'Greet'(003). + page->ele( `VBox` )->a( n = `class` v = `sapUiSmallMargin` )->tag( `Label` - )->a( n = `text` v = 'Your name'(001) + )->a( n = `text` v = lv_name_label )->a( n = `labelFor` v = `nameInput` )->tag( `Input` )->a( n = `id` v = `nameInput` )->a( n = `value` v = client->_bind( name ) - )->a( n = `placeholder` v = 'Type a name here'(002) + )->a( n = `placeholder` v = lv_placeholder )->a( n = `width` v = `20rem` )->tag( `Button` )->a( n = `press` v = client->_event( `GREET` ) - )->a( n = `text` v = 'Greet'(003) + )->a( n = `text` v = lv_greet )->a( n = `type` v = `Emphasized` )->a( n = `class` v = `sapUiSmallMarginTop` ).