Skip to content

feat: add CDS (SAP CAP) language support (.cds) - #1724

Open
nickels wants to merge 5 commits into
colbymchenry:mainfrom
nickels:feat/cds-language-support
Open

feat: add CDS (SAP CAP) language support (.cds)#1724
nickels wants to merge 5 commits into
colbymchenry:mainfrom
nickels:feat/cds-language-support

Conversation

@nickels

@nickels nickels commented Sep 6, 2026

Copy link
Copy Markdown

Part of #648 (the language support tracking issue, which consolidates individual requests). No separate CDS request issue exists.

Indexes SAP CAP CDS models (.cds) the way they are written: namespaces and contexts, services, entities and views, elements with their associations and compositions, types and enums, aspects, events, actions and functions (bound and unbound), plus using imports with alias expansion, extend and annotate directives, and annotations kept as decorators (@readonly, @requires, @restrict, @path, @UI.*, ...). Service projections link to the db entities they project, includes link to the aspects they pull in, associations link to their targets, and relative using ... from paths link files.

This is a from-scratch extractor on current main, modelled on #1685 (Elixir): definitions are dispatched in visitNode because CDS definitions carry their elements as direct children, and a small CDS branch in the name matcher resolves references by CDS scoping rules (service scope, then enclosing context and namespace, then fully qualified) using the dot-normalized qualified name.

What it extracts

  • namespace / context (namespace), service (module, members marked exported)
  • entity / view (class) including as projection on and as select from with join sources
  • elements (field) with signatures, key / virtual decorators, nested structures, enum members
  • type (type_alias or enum), aspect (interface), event (struct), annotation definitions
  • unbound action / function (function), bound actions { } (method)
  • using { a.b.C as D } from '../db/schema' alias maps, per-artifact references and file import edges
  • extend X with { ... } elements and actions rooted at the extended definition, annotate X with references
  • doc comments (/** */, //) as docstrings

Grammar

Vendored cap-js-community/tree-sitter-cds @ 66bc383 (v2.0.0, Apache-2.0) with a small local patch (docs/grammars/tree-sitter-cds.patch): empty [] / {} annotation values and annotations after default / not null, which broke 9 of 247 .cds files across 11 real CAP repos. Built with tree-sitter-cli 0.25.10 (generate + build --wasm --docker, emsdk 4.0.4, ABI 15); all 60 upstream corpus tests plus 2 new ones pass on the regenerated parser, and the patched wasm parses all 247 corpus files with no ERROR node. SAP archived the grammar repository in June 2026 and it describes itself as a highlighting grammar rather than a 100 percent compatible parser, so provenance, rebuild steps, parse health and the remaining known gaps live in docs/grammars/tree-sitter-cds.md. tree-sitter-wasms does not ship it.

Extraction (real repos)

Repo Size Files .cds Nodes Edges verify-extraction
SAP-samples/fiori-elements-feature-showcase Small 63 19 320 425 PASS
SAP-samples/btp-cap-multitenant-saas Medium 144 27 507 1,015 PASS
gregorwolf/bookshop-demo Large 766 74 7,011 14,160 PASS

Unit tests: CDS block in __tests__/extraction.test.ts plus __tests__/cds-resolution.test.ts (scoped lookup, alias FQN, self-exclusion, ambiguity, import paths) and __tests__/cds-cross-file.test.ts (end-to-end db/srv/app project).

Retrieval A/B

Two with/without runs (scripts/agent-eval/run-all.sh, Sonnet, effort high, n=1 per arm), one per corpus question on the Small and Medium repos:

Repo Arm duration tool calls Read codegraph cost
fiori-elements-feature-showcase with 43s 7 3 0 $0.31
fiori-elements-feature-showcase without 25s 4 3 0 $0.17
btp-cap-multitenant-saas with 20s 3 1 0 $0.18
btp-cap-multitenant-saas without 22s 4 2 0 $0.16

Both arms reached the right files and the right answer every time. The with-arm never called codegraph: the MCP server attached (1 tool exposed) but Sonnet grepped LROPODataService / AdminService and read the two or three .cds files that hold the whole answer. CDS models are small and grep-friendly, so a retrieval win is not the merge bar here; extraction quality (the table above, the cross-file edges, and the three flow probes below) is.

Flow probes over the index (references / implements edges, up to 4 hops): fiori LROPODataService::RootEntities -> sap.fe.showcase::RootEntities -> (includes) rootBasis -> (criticality) -> sap.common::Criticality connects; btp AdminService::Members -> susaas::db::Members -> (user) -> susaas::db::Users -> (role) -> susaas::db::Roles connects (context susaas.db is stored as scope susaas plus db; dot-normalized it is the CDS name); bookshop-demo AdminService::Books -> my.bookshop::Books -> (genre) -> my.bookshop::Genres connects (its managed and sap.common.CodeList includes come from @sap/cds/common and stay unresolved by design).

Follow-ups (not in this PR)

  • select-list columns of projections and views as nodes (renamed and calculated fields)
  • gen/ build output of cds build is not ignored by default (it can duplicate .cds sources)
  • Fiori @UI annotation semantics and srv/*.js handler wiring (srv.on('READ', 'Books', ...)) as cross-language edges
  • WHEN / branch-guard rules do not apply (declarative language)

Re-index CDS projects after upgrading.

Index SAP CAP CDS models with a vendored cap-js-community/tree-sitter-cds
grammar (66bc383, Apache-2.0, ABI 15 rebuild with a local patch for empty
annotation values and trailing element annotations).

Extraction: namespace and context (namespace), service (module, members
exported), entity and view (class) including projections and selects with
joins, elements (field) with key/virtual decorators and nested structures,
type (type_alias or enum) with enum members, aspect (interface), event
(struct), annotation definitions, unbound actions and functions (function),
bound actions (method), using imports with alias maps, extend members rooted
at the extended artifact, annotate targets, annotations as decorators and
doc comments. Dotted definition names split into scope and leaf so the
dot-normalized qualifiedName is the CDS fully qualified name.

References: includes (extends), association and composition targets,
non-builtin element and parameter types, projection and select sources,
redirected-to targets, extend and annotate targets, and each using artifact.
Language detection plus a CDS Extraction block: namespaces and qualified
names, artifact kinds, elements and enums, services and actions, annotations
and doc comments, using aliases, references, extend and annotate, dotted
definition names, per-file state, and a file the grammar trips on.
Add a terminal CDS branch to matchReference: candidates by last segment
(or the full dotted name for namespaces), compared dot-normalized against
the caller's scope chain from innermost to the file level, with a unique
bare-name fallback and no fuzzy fallthrough. Relative using-from specifiers
resolve to model files through ['.cds', '/index.cds']; package specifiers
stay external. Refs from other languages never land on CDS nodes. Named
symbol flow matches dotted CDS names against the namespace-as-one-segment
qualifiedName spelling.
Unit tests for the CDS matcher (scoped lookup, alias FQN, self-exclusion,
ambiguity, namespace aliases, action targets, language isolation) and the
using-from path resolution, plus an end-to-end test that indexes a db/srv/app
CAP project and checks every edge kind and the unresolved-by-design list.
README table row and logo, CHANGELOG entry, framework-coverage and kernel
migration rows, and three CAP repos with cross-file questions in the
agent-eval corpus.
@nickels
nickels marked this pull request as ready for review September 7, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant