abap-mcp 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -12
- package/dist/abap/engine.d.ts +24 -0
- package/dist/abap/engine.js +76 -0
- package/dist/abap/readiness.d.ts +33 -4
- package/dist/abap/readiness.js +80 -3
- package/dist/abap/released.d.ts +46 -0
- package/dist/abap/released.js +94 -0
- package/dist/abap.tools.d.ts +6 -0
- package/dist/abap.tools.js +113 -0
- package/dist/cli-commands.d.ts +2 -1
- package/dist/cli-commands.js +54 -2
- package/dist/data/released-apis.json +1 -0
- package/dist/data/table-successors.json +40 -0
- package/dist/errors.js +7 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@ an abapGit export, a code review, CI.
|
|
|
6
6
|
|
|
7
7
|
Built on [abaplint](https://abaplint.org) (the open-source ABAP parser/linter) and the
|
|
8
8
|
[Model Context Protocol](https://modelcontextprotocol.io). TypeScript, 100% local — the server
|
|
9
|
-
makes **zero network calls** and
|
|
10
|
-
back as structured JSON.
|
|
9
|
+
makes **zero network calls** and reads **no user files**: sources go in as text, findings come
|
|
10
|
+
back as structured JSON. (The released-API list and abaplint's rule data are package-bundled
|
|
11
|
+
assets that ship inside the install — no network, no user filesystem, at runtime.)
|
|
11
12
|
|
|
12
13
|
## Why this exists
|
|
13
14
|
|
|
@@ -19,6 +20,7 @@ layer:
|
|
|
19
20
|
|
|
20
21
|
- *"Does this ABAP parse? Is it clean?"* → `lint_abap`
|
|
21
22
|
- *"How far is this classic report from ABAP Cloud?"* → `check_cloud_readiness`
|
|
23
|
+
- *"Is MARA a released API? What do I use instead?"* → `check_released_api`
|
|
22
24
|
- *"Start me a correct RAP business object."* → `scaffold_rap_bo`
|
|
23
25
|
- *"What's in this 4,000-line class?"* → `get_abap_outline`
|
|
24
26
|
|
|
@@ -55,6 +57,7 @@ npx abap-mcp lint src/ # lint files or whole directorie
|
|
|
55
57
|
npx abap-mcp readiness src/ --fail-below 80 # repo-level ABAP Cloud readiness, CI-gateable
|
|
56
58
|
npx abap-mcp scaffold --entity Travel --table ztravel --key travel_id --out ./out
|
|
57
59
|
npx abap-mcp outline src/zcl_monster.clas.abap # navigate big objects
|
|
60
|
+
npx abap-mcp released MARA I_Product # released-API status + CDS successor
|
|
58
61
|
npx abap-mcp explain exit_or_check # rule rationale
|
|
59
62
|
```
|
|
60
63
|
|
|
@@ -75,7 +78,8 @@ loop condition), per-repo `.mcp.json`, and a GitHub Actions quality gate for aba
|
|
|
75
78
|
| Tool | What it does |
|
|
76
79
|
| --- | --- |
|
|
77
80
|
| `lint_abap` | abaplint static analysis over ABAP/CDS/BDEF sources → structured findings with rule docs links. Presets: `style` (default, snippet-friendly), `full`, `syntax-only`; per-rule overrides. |
|
|
78
|
-
| `check_cloud_readiness` | Dual-parse diff (classic baseline vs `Cloud`): statements that are valid today but illegal in ABAP Cloud become categorized blockers (dynpro, list output, native SQL, …) with a transparent score; code broken at the baseline is reported separately, not counted as migration work. |
|
|
81
|
+
| `check_cloud_readiness` | Dual-parse diff (classic baseline vs `Cloud`): statements that are valid today but illegal in ABAP Cloud become categorized blockers (dynpro, list output, native SQL, …) with a transparent score; code broken at the baseline is reported separately, not counted as migration work. Now also surfaces a **separate, dated released-API cross-check** (`releasedApiFindings`): direct access to non-released classic tables and deprecated-API usage found in the source, with CDS successor hints — informational, not folded into the score. |
|
|
82
|
+
| `check_released_api` | Looks up objects (tables, CDS views, function modules, classes, …) in SAP's bundled Cloudification snapshot → `released` / `deprecated` / `not-released` per object, plus a curated CDS successor for common classic tables. The released-API half of readiness, offline. |
|
|
79
83
|
| `scaffold_rap_bo` | Generates the canonical RAP managed-BO stack (root view, behavior definition `strict(2)` + optional draft, behavior class + handler locals, projection, metadata extension, OData V4 service definition) plus suggested table DDL, activation order and next steps. |
|
|
80
84
|
| `list_abap_rules` | Browse abaplint's ~180 rules (filter by text or tag). |
|
|
81
85
|
| `explain_abap_rule` | One rule in depth — rationale (often Clean ABAP), examples, docs URL. |
|
|
@@ -84,25 +88,29 @@ loop condition), per-repo `.mcp.json`, and a GitHub Actions quality gate for aba
|
|
|
84
88
|
|
|
85
89
|
## Honesty box — what this is *not*
|
|
86
90
|
|
|
87
|
-
- **Not ATC.**
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
+
- **Not ATC.** The objective readiness *score* is still language-level: statements ABAP Cloud
|
|
92
|
+
removed. Released-API coverage is now **partial and offline**: `check_released_api` and the
|
|
93
|
+
`releasedApiFindings` in readiness reflect SAP's published Cloudification list *as of the
|
|
94
|
+
bundled snapshot date* — they cover tables and function modules referenced in your source, not
|
|
95
|
+
every API, and are only as current as the snapshot. A target system's own released-API list
|
|
96
|
+
(ATC check `API_RELEASE_STATE_CHECK` / `SAP_CP_READINESS`) remains authoritative; treat an
|
|
97
|
+
"absent from the list" result as "not released as of the snapshot", not as proof.
|
|
91
98
|
- **Scaffold validation is tiered.** Generated classes and CDS views are round-tripped through
|
|
92
99
|
abaplint at Cloud level before they're returned (the generator and the linter share one
|
|
93
100
|
parser). Behavior/service definitions are outside abaplint's checked surface — they are
|
|
94
101
|
golden-tested canonical templates, and ADT activation is the final arbiter. Each generated
|
|
95
102
|
file is labeled `validated: "abaplint" | "template"`.
|
|
96
|
-
- **Text-in only, by design.** No filesystem walking, no network — the entire attack
|
|
97
|
-
a parser over strings you explicitly pass.
|
|
98
|
-
|
|
103
|
+
- **Text-in only, by design.** No user-filesystem walking, no network — the entire attack
|
|
104
|
+
surface is a parser over strings you explicitly pass. (The released-API snapshot and abaplint's
|
|
105
|
+
rule data are package-bundled assets imported from the install, not fetched or read from your
|
|
106
|
+
disk.) For linting whole directories, use the [abaplint CLI](https://abaplint.org) in CI, or the
|
|
99
107
|
[mcp-kit `wrap-abaplint` recipe](https://github.com/palimkarakshay/mcp-kit) this server grew out of.
|
|
100
108
|
|
|
101
109
|
## Develop
|
|
102
110
|
|
|
103
111
|
```bash
|
|
104
112
|
npm install
|
|
105
|
-
npm run check # typecheck +
|
|
113
|
+
npm run check # typecheck + 121 tests + build — the CI gate
|
|
106
114
|
node dist/cli.js # stdio MCP server
|
|
107
115
|
npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
|
|
108
116
|
```
|
|
@@ -110,7 +118,7 @@ npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/list
|
|
|
110
118
|
Tool descriptions are CI-graded (a rubric test enforces verb-first names, when-to-use,
|
|
111
119
|
non-goals, described params, worked examples — the
|
|
112
120
|
[mcp-kit](https://github.com/palimkarakshay/mcp-kit) discipline; the full mcp-kit lint scores all
|
|
113
|
-
|
|
121
|
+
eight tools 100/100).
|
|
114
122
|
|
|
115
123
|
## Design
|
|
116
124
|
|
|
@@ -122,6 +130,11 @@ scaffolder validates its own output, what was deliberately left out — lives in
|
|
|
122
130
|
|
|
123
131
|
- [abaplint](https://github.com/abaplint/abaplint) by Lars Hvam — the parser and rule engine
|
|
124
132
|
underneath every tool here (MIT).
|
|
133
|
+
- [SAP/abap-atc-cr-cv-s4hc](https://github.com/SAP/abap-atc-cr-cv-s4hc) — SAP's official ABAP
|
|
134
|
+
Cloudification Repository (object release list), **Apache-2.0**. The bundled released-API
|
|
135
|
+
snapshot (`src/data/released-apis.json`, snapshot **2026-06-10**) is a compact transform of
|
|
136
|
+
that data, redistributed under Apache-2.0 with attribution; see
|
|
137
|
+
[docs/DESIGN.md](docs/DESIGN.md) and `scripts/build-released-api-index.mjs` for the pipeline.
|
|
125
138
|
- [mcp-kit](https://github.com/palimkarakshay/mcp-kit) — the production-MCP patterns this server
|
|
126
139
|
follows (typed tool specs, transport discipline, description lint).
|
|
127
140
|
|
package/dist/abap/engine.d.ts
CHANGED
|
@@ -44,3 +44,27 @@ export interface RunResult {
|
|
|
44
44
|
fileCount: number;
|
|
45
45
|
}
|
|
46
46
|
export declare function runAbaplint(files: AbapSource[], opts: RunOptions): RunResult;
|
|
47
|
+
/** A statically-resolved reference to a repository object found in the source. */
|
|
48
|
+
export interface ObjectReference {
|
|
49
|
+
/** The referenced object name (upper-cased as written; tables are typically lowercased in source). */
|
|
50
|
+
name: string;
|
|
51
|
+
/** SAP object type: "TABL" for DB tables, "FUNC" for function modules. */
|
|
52
|
+
objectType: "TABL" | "FUNC";
|
|
53
|
+
/** How the reference appears, for the human-facing note. */
|
|
54
|
+
kind: "db-access" | "call-function";
|
|
55
|
+
file: string;
|
|
56
|
+
line: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Walk the parsed AST and collect references to repository objects we can
|
|
60
|
+
* resolve reliably: DB tables in ABAP-SQL statements (SELECT/INSERT/UPDATE/
|
|
61
|
+
* DELETE/MODIFY, including the tables in joins and FROM clauses) and function
|
|
62
|
+
* modules in CALL FUNCTION '<fm>'. Parsing is done at a classic baseline so the
|
|
63
|
+
* statements resolve even for code ABAP Cloud would reject; abaplint only
|
|
64
|
+
* parses, never executes. References are de-duplicated by name+type+location.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately conservative: we extract only what the parser exposes as a
|
|
67
|
+
* first-class expression (DatabaseTable, FunctionName), not heuristic regexes,
|
|
68
|
+
* so a reference we report is a reference that exists.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractObjectReferences(files: AbapSource[], baselineVersion?: AbapVersion): ObjectReference[];
|
package/dist/abap/engine.js
CHANGED
|
@@ -141,3 +141,79 @@ export function runAbaplint(files, opts) {
|
|
|
141
141
|
});
|
|
142
142
|
return { findings, truncated: issues.length > MAX_FINDINGS, fileCount: bounded.length };
|
|
143
143
|
}
|
|
144
|
+
const SQL_STATEMENTS = new Set([
|
|
145
|
+
"Select",
|
|
146
|
+
"SelectLoop",
|
|
147
|
+
"InsertDatabase",
|
|
148
|
+
"UpdateDatabase",
|
|
149
|
+
"DeleteDatabase",
|
|
150
|
+
"ModifyDatabase",
|
|
151
|
+
]);
|
|
152
|
+
/**
|
|
153
|
+
* Walk the parsed AST and collect references to repository objects we can
|
|
154
|
+
* resolve reliably: DB tables in ABAP-SQL statements (SELECT/INSERT/UPDATE/
|
|
155
|
+
* DELETE/MODIFY, including the tables in joins and FROM clauses) and function
|
|
156
|
+
* modules in CALL FUNCTION '<fm>'. Parsing is done at a classic baseline so the
|
|
157
|
+
* statements resolve even for code ABAP Cloud would reject; abaplint only
|
|
158
|
+
* parses, never executes. References are de-duplicated by name+type+location.
|
|
159
|
+
*
|
|
160
|
+
* Deliberately conservative: we extract only what the parser exposes as a
|
|
161
|
+
* first-class expression (DatabaseTable, FunctionName), not heuristic regexes,
|
|
162
|
+
* so a reference we report is a reference that exists.
|
|
163
|
+
*/
|
|
164
|
+
export function extractObjectReferences(files, baselineVersion = "v758") {
|
|
165
|
+
const bounded = boundFiles(files);
|
|
166
|
+
const registry = new abaplint.Registry(buildConfig({ version: baselineVersion, preset: "syntax-only" }));
|
|
167
|
+
for (const f of bounded) {
|
|
168
|
+
registry.addFile(new abaplint.MemoryFile(f.filename, f.source));
|
|
169
|
+
}
|
|
170
|
+
registry.parse();
|
|
171
|
+
const refs = [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const push = (ref) => {
|
|
174
|
+
const dedupeKey = `${ref.objectType}:${ref.name}:${ref.file}:${ref.line}`;
|
|
175
|
+
if (seen.has(dedupeKey))
|
|
176
|
+
return;
|
|
177
|
+
seen.add(dedupeKey);
|
|
178
|
+
refs.push(ref);
|
|
179
|
+
};
|
|
180
|
+
for (const obj of registry.getObjects()) {
|
|
181
|
+
if (!(obj instanceof abaplint.ABAPObject))
|
|
182
|
+
continue;
|
|
183
|
+
for (const file of obj.getABAPFiles()) {
|
|
184
|
+
const filename = file.getFilename();
|
|
185
|
+
for (const st of file.getStatements()) {
|
|
186
|
+
const type = st.get().constructor.name;
|
|
187
|
+
if (SQL_STATEMENTS.has(type)) {
|
|
188
|
+
for (const e of st.findAllExpressions(abaplint.Expressions.DatabaseTable)) {
|
|
189
|
+
const name = e.concatTokens().trim();
|
|
190
|
+
if (name.length === 0)
|
|
191
|
+
continue;
|
|
192
|
+
push({
|
|
193
|
+
name: name.toUpperCase(),
|
|
194
|
+
objectType: "TABL",
|
|
195
|
+
kind: "db-access",
|
|
196
|
+
file: filename,
|
|
197
|
+
line: e.getFirstToken().getStart().getRow(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (type === "CallFunction") {
|
|
202
|
+
for (const e of st.findAllExpressions(abaplint.Expressions.FunctionName)) {
|
|
203
|
+
const name = e.concatTokens().replace(/'/g, "").trim();
|
|
204
|
+
if (name.length === 0)
|
|
205
|
+
continue;
|
|
206
|
+
push({
|
|
207
|
+
name: name.toUpperCase(),
|
|
208
|
+
objectType: "FUNC",
|
|
209
|
+
kind: "call-function",
|
|
210
|
+
file: filename,
|
|
211
|
+
line: e.getFirstToken().getStart().getRow(),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return refs;
|
|
219
|
+
}
|
package/dist/abap/readiness.d.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* longer allows). A finding present at the baseline is just *broken code* —
|
|
8
8
|
* reporting it as a migration item would overstate the migration.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Released-API check: separately from the parser-level diff, the source is
|
|
11
|
+
* walked for object references (DB tables, function modules) which are looked
|
|
12
|
+
* up against the bundled SAP Cloudification snapshot. These land in their own
|
|
13
|
+
* `releasedApiFindings` field — they are NOT folded into cloudBlockerCount or
|
|
14
|
+
* score, because those are objective, parser-level numbers and the snapshot is
|
|
15
|
+
* only as current as its date.
|
|
14
16
|
*/
|
|
15
17
|
import type { AbapSource, AbapVersion, Finding } from "./engine.js";
|
|
16
18
|
export interface ReadinessCategory {
|
|
@@ -19,6 +21,25 @@ export interface ReadinessCategory {
|
|
|
19
21
|
count: number;
|
|
20
22
|
findings: Finding[];
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A released-API observation about a referenced object. Kept SEPARATE from the
|
|
26
|
+
* parser-level blocker counts/score: it reflects the bundled SAP snapshot
|
|
27
|
+
* (dated), not abaplint's objective parse, and a system's ATC is authoritative.
|
|
28
|
+
*/
|
|
29
|
+
export interface ReleasedApiFinding {
|
|
30
|
+
/** Referenced object name (upper-cased). */
|
|
31
|
+
object: string;
|
|
32
|
+
/** SAP object type: "TABL" (DB table) or "FUNC" (function module). */
|
|
33
|
+
objectType: string;
|
|
34
|
+
/** Released-API state from the bundled snapshot. */
|
|
35
|
+
state: "deprecated" | "not-released";
|
|
36
|
+
/** Curated released CDS successor for a classic table, when known. */
|
|
37
|
+
successor?: string;
|
|
38
|
+
file: string;
|
|
39
|
+
line: number;
|
|
40
|
+
/** Human-facing explanation of why this was flagged. */
|
|
41
|
+
note: string;
|
|
42
|
+
}
|
|
22
43
|
export interface ReadinessReport {
|
|
23
44
|
verdict: "ready" | "minor-rework" | "moderate-rework" | "significant-rework";
|
|
24
45
|
score: number;
|
|
@@ -26,6 +47,14 @@ export interface ReadinessReport {
|
|
|
26
47
|
categories: ReadinessCategory[];
|
|
27
48
|
/** Findings that fail even at the classic baseline — fix these first; they are not migration items. */
|
|
28
49
|
brokenAtBaseline: Finding[];
|
|
50
|
+
/**
|
|
51
|
+
* Released-API observations from the bundled SAP Cloudification snapshot —
|
|
52
|
+
* deprecated API usage and direct access to non-released (classic) tables,
|
|
53
|
+
* with successor hints. Separate from cloudBlockerCount/score by design.
|
|
54
|
+
*/
|
|
55
|
+
releasedApiFindings: ReleasedApiFinding[];
|
|
56
|
+
/** Date of the bundled released-API snapshot the releasedApiFindings reflect. */
|
|
57
|
+
releasedApiSnapshotDate: string;
|
|
29
58
|
baselineVersion: AbapVersion;
|
|
30
59
|
scopeNote: string;
|
|
31
60
|
}
|
package/dist/abap/readiness.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { runAbaplint } from "./engine.js";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { extractObjectReferences, runAbaplint } from "./engine.js";
|
|
2
|
+
import { lookupReleased, RELEASED_API_SNAPSHOT, suggestSuccessor } from "./released.js";
|
|
3
|
+
export const SCOPE_NOTE = "Static parser-level analysis (abaplint) PLUS a released-API cross-check against SAP's bundled Cloudification " +
|
|
4
|
+
`snapshot (dated ${RELEASED_API_SNAPSHOT.snapshotDate}). It detects statements ABAP Cloud removes (the objective ` +
|
|
5
|
+
"cloud-blocker count and score) and, separately, flags deprecated-API usage and direct access to non-released " +
|
|
6
|
+
"classic tables (releasedApiFindings — informational, NOT counted in the score). The bundled list is only as current " +
|
|
7
|
+
"as its snapshot date, and covers tables and function modules referenced here, not every API; a target system's own " +
|
|
8
|
+
"released-API list (ATC check API_RELEASE_STATE_CHECK / SAP_CP_READINESS) remains authoritative. " +
|
|
4
9
|
"Treat 'ready' as 'no language-level blockers', not as a full Clean Core certification.";
|
|
5
10
|
/** Map an offending line to a human category by its leading keyword(s). */
|
|
6
11
|
function categorize(excerpt) {
|
|
@@ -50,7 +55,79 @@ export function checkCloudReadiness(files, baselineVersion = "v758") {
|
|
|
50
55
|
cloudBlockerCount: n,
|
|
51
56
|
categories: [...byCategory.values()].sort((a, b) => b.count - a.count),
|
|
52
57
|
brokenAtBaseline: baseline.findings,
|
|
58
|
+
releasedApiFindings: computeReleasedApiFindings(files, baselineVersion),
|
|
59
|
+
releasedApiSnapshotDate: RELEASED_API_SNAPSHOT.snapshotDate,
|
|
53
60
|
baselineVersion,
|
|
54
61
|
scopeNote: SCOPE_NOTE,
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Cross-check statically-extracted object references against the bundled SAP
|
|
66
|
+
* Cloudification snapshot. Flags two cases:
|
|
67
|
+
* - `deprecated` — the referenced object is a deprecated released API.
|
|
68
|
+
* - `not-released` — the reference is explicitly recorded as notToBeReleased
|
|
69
|
+
* in SAP's list: direct access to a classic/internal table (the typical
|
|
70
|
+
* "SELECT … FROM mara" case, with a curated CDS successor hint when one is
|
|
71
|
+
* known) or a CALL FUNCTION to an internal-only function module.
|
|
72
|
+
* Released objects and references the snapshot does not recognise are silent —
|
|
73
|
+
* absence from the list (every customer Z/Y-object, for a start) is "not known
|
|
74
|
+
* to be a problem", not proof either way.
|
|
75
|
+
*/
|
|
76
|
+
function computeReleasedApiFindings(files, baselineVersion) {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const ref of extractObjectReferences(files, baselineVersion)) {
|
|
79
|
+
let hit = lookupReleased(ref.name, ref.objectType);
|
|
80
|
+
// An ABAP-SQL FROM clause names either a DDIC table or a CDS entity, but
|
|
81
|
+
// the extractor labels both TABL — fall back to the CDS record so a
|
|
82
|
+
// deprecated CDS view in a SELECT is still caught.
|
|
83
|
+
if (!hit.recorded && ref.objectType === "TABL") {
|
|
84
|
+
hit = lookupReleased(ref.name, "CDS_STOB");
|
|
85
|
+
}
|
|
86
|
+
if (hit.state === "released")
|
|
87
|
+
continue;
|
|
88
|
+
if (hit.state === "deprecated") {
|
|
89
|
+
out.push({
|
|
90
|
+
object: ref.name,
|
|
91
|
+
objectType: ref.objectType,
|
|
92
|
+
state: "deprecated",
|
|
93
|
+
file: ref.file,
|
|
94
|
+
line: ref.line,
|
|
95
|
+
note: ref.kind === "db-access"
|
|
96
|
+
? `${ref.name} is a deprecated released object as of the snapshot — migrate to its current successor before going to ABAP Cloud.`
|
|
97
|
+
: `Function module ${ref.name} is deprecated as of the snapshot — replace it with the released successor API.`,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// not-released. Only flag names SAP's snapshot explicitly records as
|
|
102
|
+
// notToBeReleased — a name merely absent from the list (every customer
|
|
103
|
+
// Z/Y-object, for a start) is silent: absence is "not known to be a
|
|
104
|
+
// problem", not evidence of one.
|
|
105
|
+
if (!hit.recorded)
|
|
106
|
+
continue;
|
|
107
|
+
if (ref.objectType === "FUNC") {
|
|
108
|
+
out.push({
|
|
109
|
+
object: ref.name,
|
|
110
|
+
objectType: ref.objectType,
|
|
111
|
+
state: "not-released",
|
|
112
|
+
file: ref.file,
|
|
113
|
+
line: ref.line,
|
|
114
|
+
note: `Function module ${ref.name} is recorded as not-to-be-released in SAP's Cloudification list — it will not become a public API in ABAP Cloud; use a released successor API instead.`,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ref.objectType === "TABL" && hit.objectType === "TABL") {
|
|
119
|
+
const successor = suggestSuccessor(ref.name);
|
|
120
|
+
out.push({
|
|
121
|
+
object: ref.name,
|
|
122
|
+
objectType: ref.objectType,
|
|
123
|
+
state: "not-released",
|
|
124
|
+
...(successor !== undefined ? { successor } : {}),
|
|
125
|
+
file: ref.file,
|
|
126
|
+
line: ref.line,
|
|
127
|
+
note: `${ref.name} is not a released API — direct access to this classic table is not allowed in ABAP Cloud.` +
|
|
128
|
+
(successor !== undefined ? ` Use the released CDS view ${successor} instead.` : " Use a released CDS view instead."),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Snapshot metadata for the bundled released-API list. */
|
|
2
|
+
export declare const RELEASED_API_SNAPSHOT: {
|
|
3
|
+
readonly snapshotDate: string;
|
|
4
|
+
readonly source: string;
|
|
5
|
+
readonly formatVersion: string | null;
|
|
6
|
+
readonly recordCount: number;
|
|
7
|
+
};
|
|
8
|
+
export type ReleasedState = "released" | "deprecated" | "not-released";
|
|
9
|
+
export interface ReleasedLookup {
|
|
10
|
+
name: string;
|
|
11
|
+
/** The object type recorded by SAP (TABL, CDS_STOB, FUNC, CLAS, …), or the queried type when no record matched. */
|
|
12
|
+
objectType: string | undefined;
|
|
13
|
+
state: ReleasedState;
|
|
14
|
+
applicationComponent?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* true when the name was found in SAP's snapshot (under the requested type,
|
|
17
|
+
* if one was given). false means absent — "not released as of the snapshot"
|
|
18
|
+
* by omission, which is weaker evidence than an explicit notToBeReleased
|
|
19
|
+
* record and must not be reported as a violation on its own.
|
|
20
|
+
*/
|
|
21
|
+
recorded: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Look up an object in the bundled released-API list. Case-insensitive.
|
|
25
|
+
*
|
|
26
|
+
* `not-released` means "not a released API as of the snapshot": either the name
|
|
27
|
+
* is absent from SAP's list, or it is present with state `notToBeReleased`
|
|
28
|
+
* (typical for classic DDIC tables). A `released`/`deprecated` result is taken
|
|
29
|
+
* verbatim from SAP's published data.
|
|
30
|
+
*
|
|
31
|
+
* When `objectType` is given the lookup is strict: only a record of exactly
|
|
32
|
+
* that type answers the query — a same-named record under a different type is
|
|
33
|
+
* a miss (`recorded: false`), never a substitute (a released class must not
|
|
34
|
+
* make a non-released table look released). Untyped lookups use the first
|
|
35
|
+
* recorded entry, preferring a `released` or `deprecated` record over a
|
|
36
|
+
* `notToBeReleased` one so a genuinely released API is never masked by a
|
|
37
|
+
* same-named internal object.
|
|
38
|
+
*/
|
|
39
|
+
export declare function lookupReleased(objectName: string, objectType?: string): ReleasedLookup;
|
|
40
|
+
/**
|
|
41
|
+
* Suggest the canonical released CDS view-entity successor for a classic DB
|
|
42
|
+
* table, from the curated table-successors map. Case-insensitive. Returns
|
|
43
|
+
* undefined when no curated successor is known (the caller should fall back to
|
|
44
|
+
* the target system's released-API list).
|
|
45
|
+
*/
|
|
46
|
+
export declare function suggestSuccessor(tableName: string): string | undefined;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Released-API lookup against SAP's published ABAP Cloudification list.
|
|
3
|
+
*
|
|
4
|
+
* The data shipped at src/data/released-apis.json is a compact transform of
|
|
5
|
+
* SAP's official, Apache-2.0 object release list (SAP/abap-atc-cr-cv-s4hc),
|
|
6
|
+
* built offline by scripts/build-released-api-index.mjs. It is a PACKAGE-BUNDLED
|
|
7
|
+
* asset (like abaplint's own bundled rule metadata): importing it touches no
|
|
8
|
+
* network and no user filesystem at runtime, so it does not violate the
|
|
9
|
+
* server's "text in, JSON out, offline" contract.
|
|
10
|
+
*
|
|
11
|
+
* SAP's list uses three states:
|
|
12
|
+
* - "released" — a released API, safe to consume in ABAP Cloud.
|
|
13
|
+
* - "deprecated" — was released, now on the way out; move to a successor.
|
|
14
|
+
* - "notToBeReleased" — will not be released as a public API (classic DDIC
|
|
15
|
+
* tables, internal objects); direct use is a cloud
|
|
16
|
+
* blocker. We surface these (and names absent from the
|
|
17
|
+
* list entirely) as state "not-released".
|
|
18
|
+
*/
|
|
19
|
+
import index from "../data/released-apis.json" with { type: "json" };
|
|
20
|
+
import successorData from "../data/table-successors.json" with { type: "json" };
|
|
21
|
+
const data = index;
|
|
22
|
+
const successors = successorData;
|
|
23
|
+
/** Snapshot metadata for the bundled released-API list. */
|
|
24
|
+
export const RELEASED_API_SNAPSHOT = {
|
|
25
|
+
snapshotDate: data.snapshotDate,
|
|
26
|
+
source: data.source,
|
|
27
|
+
formatVersion: data.formatVersion,
|
|
28
|
+
recordCount: data.recordCount,
|
|
29
|
+
};
|
|
30
|
+
/** Map SAP's raw state to our three-value state. */
|
|
31
|
+
function toState(rawState) {
|
|
32
|
+
if (rawState === "released")
|
|
33
|
+
return "released";
|
|
34
|
+
if (rawState === "deprecated")
|
|
35
|
+
return "deprecated";
|
|
36
|
+
// "notToBeReleased" — present in the list but never a public API.
|
|
37
|
+
return "not-released";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Look up an object in the bundled released-API list. Case-insensitive.
|
|
41
|
+
*
|
|
42
|
+
* `not-released` means "not a released API as of the snapshot": either the name
|
|
43
|
+
* is absent from SAP's list, or it is present with state `notToBeReleased`
|
|
44
|
+
* (typical for classic DDIC tables). A `released`/`deprecated` result is taken
|
|
45
|
+
* verbatim from SAP's published data.
|
|
46
|
+
*
|
|
47
|
+
* When `objectType` is given the lookup is strict: only a record of exactly
|
|
48
|
+
* that type answers the query — a same-named record under a different type is
|
|
49
|
+
* a miss (`recorded: false`), never a substitute (a released class must not
|
|
50
|
+
* make a non-released table look released). Untyped lookups use the first
|
|
51
|
+
* recorded entry, preferring a `released` or `deprecated` record over a
|
|
52
|
+
* `notToBeReleased` one so a genuinely released API is never masked by a
|
|
53
|
+
* same-named internal object.
|
|
54
|
+
*/
|
|
55
|
+
export function lookupReleased(objectName, objectType) {
|
|
56
|
+
const key = objectName.trim().toUpperCase();
|
|
57
|
+
const entries = data.objects[key];
|
|
58
|
+
const wantedType = objectType?.trim().toUpperCase();
|
|
59
|
+
if (entries === undefined || entries.length === 0) {
|
|
60
|
+
return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
|
|
61
|
+
}
|
|
62
|
+
let chosen;
|
|
63
|
+
if (wantedType !== undefined) {
|
|
64
|
+
chosen = entries.find((e) => e[0].toUpperCase() === wantedType);
|
|
65
|
+
if (chosen === undefined) {
|
|
66
|
+
// Typed query, no record of that type: a miss, not a cross-type answer.
|
|
67
|
+
return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Prefer a released/deprecated record over notToBeReleased when ambiguous.
|
|
72
|
+
chosen =
|
|
73
|
+
entries.find((e) => e[1] === "released" || e[1] === "deprecated") ?? entries[0];
|
|
74
|
+
}
|
|
75
|
+
if (chosen === undefined) {
|
|
76
|
+
return { name: objectName, objectType: wantedType, state: "not-released", recorded: false };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
name: objectName,
|
|
80
|
+
objectType: chosen[0],
|
|
81
|
+
state: toState(chosen[1]),
|
|
82
|
+
applicationComponent: chosen[2],
|
|
83
|
+
recorded: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Suggest the canonical released CDS view-entity successor for a classic DB
|
|
88
|
+
* table, from the curated table-successors map. Case-insensitive. Returns
|
|
89
|
+
* undefined when no curated successor is known (the caller should fall back to
|
|
90
|
+
* the target system's released-API list).
|
|
91
|
+
*/
|
|
92
|
+
export function suggestSuccessor(tableName) {
|
|
93
|
+
return successors.successors[tableName.trim().toUpperCase()];
|
|
94
|
+
}
|
package/dist/abap.tools.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ export declare const getAbapOutline: import("./tool.js").ToolSpec<{
|
|
|
82
82
|
source: z.ZodString;
|
|
83
83
|
}, z.core.$strip>>;
|
|
84
84
|
}>;
|
|
85
|
+
export declare const checkReleasedApiTool: import("./tool.js").ToolSpec<{
|
|
86
|
+
objects: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
87
|
+
name: z.ZodString;
|
|
88
|
+
type: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>]>>;
|
|
90
|
+
}>;
|
|
85
91
|
/** Every tool this server exposes. (`tools` alias = the registry-export shape @mcp-kit/lint discovers.) */
|
|
86
92
|
export declare const ALL_TOOLS: readonly AnyToolSpec[];
|
|
87
93
|
export declare const tools: readonly AnyToolSpec[];
|