Skip to content

Commit

Permalink
Merge branch 'main' into fix/namespace-enums
Browse files Browse the repository at this point in the history
  • Loading branch information
daogrady authored Dec 19, 2023
2 parents a37dc48 + 781bbee commit 41e3a61
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 0.15.0 - TBD
### Added
- Support for [scoped entities](https://cap.cloud.sap/docs/cds/cdl#scoped-names)

### Fixed
- Inline enums are now available during runtime as well

Expand Down
1 change: 1 addition & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const getPluralAnnotation = (csn) => csn[annotations.plural.find(a => Object.has
* unlocalize("{i18n>Foo}") -> "Foo"
* @param {string} name the entity name (singular or plural).
* @returns {string} the name without localisation syntax or untouched.
* @deprecated we have dropped this feature altogether, users specify custom names via @singular/@plural now
*/
const unlocalize = (name) => {
const match = name.match(/\{i18n>(.*)\}/)
Expand Down
14 changes: 8 additions & 6 deletions lib/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,13 @@ class Visitor {
const file = this.getNamespaceFile(ns)
// entities are expected to be in plural anyway, so we would favour the regular name.
// If the user decides to pass a @plural annotation, that gets precedence over the regular name.
let plural = util.unlocalize(
this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
)
const singular = util.unlocalize(util.singular4(entity, true))
if (singular === plural) {
let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
const singular = this.resolver.trimNamespace(util.singular4(entity, true))
// trimNamespace does not properly detect scoped entities, like A.B where both A and B are
// entities. So to see if we would run into a naming collision, we forcefully take the last
// part of the name, so "A.B" and "A.Bs" just become "B" and "Bs" to be compared.
// FIXME: put this in a util function
if (singular.split('.').at(-1) === plural.split('.').at(-1)) {
plural += '_'
this.logger.warning(
`Derived singular and plural forms for '${singular}' are the same. This usually happens when your CDS entities are named following singular flexion. Consider naming your entities in plural or providing '@singular:'/ '@plural:' annotations to have a clear distinction between the two. Plural form will be renamed to '${plural}' to avoid compilation errors within the output.`
Expand Down Expand Up @@ -281,7 +283,7 @@ class Visitor {
docify(entity.doc).forEach((d) => buffer.add(d))
}

this.#aspectify(name, entity, file.classes, singular)
this.#aspectify(name, entity, buffer, singular)

// PLURAL
if (plural.includes('.')) {
Expand Down
28 changes: 27 additions & 1 deletion test/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const kinds = {
PropertyDeclaration: 'propertyDeclaration',
Keyword: 'keyword',
VariableStatement: 'variableStatement',
TypeAliasDeclaration: 'typeAliasDeclaration'
TypeAliasDeclaration: 'typeAliasDeclaration',
ModuleDeclaration: 'moduleDeclaration'
}

/*
Expand Down Expand Up @@ -56,6 +57,7 @@ const visitors = [
// order in some cases important. For example,
// ts.isStatement will be true for ImportDeclarations etc.
// so it has to be added after more specific checks.
[ts.isModuleDeclaration, visitModuleDeclaration],
[ts.isObjectLiteralExpression, visitObjectLiteralExpression],
[ts.isClassDeclaration, visitClassDeclaration],
[ts.isFunctionDeclaration, visitFunctionDeclaration],
Expand All @@ -78,6 +80,19 @@ const visitors = [
[() => true, node => console.error(`unhandled node type: ${JSON.stringify(node, null, 2)}`)]
]

/**
* @typedef {{name: string, body: any[]}} ModuleDeclaration
* @param node {ts.ModuleDeclaration}
* @returns {ModuleDeclaration}
*/
function visitModuleDeclaration(node) {
return {
nodeType: kinds.ModuleDeclaration,
name: visit(node.name),
body: node.body.statements.map(visit)
}
}

/**
* @typedef {{name: string, type: any[]}} TypeAliasDeclaration
* @param node {ts.TypeAliasDeclaration}
Expand Down Expand Up @@ -297,6 +312,17 @@ class ASTWrapper {
.filter(n => n.nodeType === kinds.TypeAliasDeclaration)
}

/** @returns {ModuleDeclaration[]} */
getModuleDeclarations() {
return this.tree
.filter(n => n.nodeType === kinds.ModuleDeclaration)
}

/** @returns {ModuleDeclaration | undefined} */
getModuleDeclaration(name) {
return this.getModuleDeclarations().find(m => m.name === name)
}

// /** @returns {ClassDeclaration[]} */
// getSingularClassDeclarations() {
// return this.getTopLevelClassDeclarations()
Expand Down
14 changes: 14 additions & 0 deletions test/unit/files/scoped/model.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace scoped;

entity Name.Something {
key something: String;
}

entity Name.SomethingElse {
key something: String;
}

entity Name {
key something: String;
somethingElse: Association to Name.SomethingElse
}
30 changes: 30 additions & 0 deletions test/unit/scoped.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'

const fs = require('fs').promises
const path = require('path')
const cds2ts = require('../../lib/compile')
const { ASTWrapper } = require('../ast')
const { locations } = require('../util')

const dir = locations.testOutput('scoped_test')

describe('Scoped Entities', () => {
let astw

beforeEach(async () => await fs.unlink(dir).catch(() => {}))
beforeAll(async () => {
const paths = await cds2ts
.compileFromFile(locations.unit.files('scoped/model.cds'), { outputDirectory: dir, inlineDeclarations: 'structured' })
astw = new ASTWrapper(path.join(paths[1], 'index.ts'))
})

test('Namespace Exists', () => expect(astw.getModuleDeclaration('Name')).toBeTruthy())
test('Namespace Entity Exists', () => expect(astw.getAspect('_NameAspect')).toBeTruthy())

test('Entities Present Within Namespace', () => {
const namespace = astw.getModuleDeclaration('Name')
expect(namespace).toBeTruthy()
expect(namespace.body.find(e => e.name === 'Something')).toBeTruthy()
expect(namespace.body.find(e => e.name === 'Something_')).toBeTruthy()
})
})

0 comments on commit 41e3a61

Please sign in to comment.