New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@cap-js/cds-typer

Package Overview
Dependencies
Maintainers
2
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cap-js/cds-typer - npm Package Compare versions

Comparing version 0.14.0 to 0.15.0

lib/components/identifier.js

15

CHANGELOG.md

@@ -7,4 +7,15 @@ # Change Log

## Version 0.15.0 - TBD
## Version 0.16.0 - TBD
## Version 0.15.0 - 2023-12-21
### Added
- Support for [scoped entities](https://cap.cloud.sap/docs/cds/cdl#scoped-names)
- Support for [delimited identifiers](https://cap.cloud.sap/docs/cds/cdl#delimited-identifiers)
### Fixed
- Inline enums are now available during runtime as well
- Inline enums can now be used as action parameter types as well. These enums will not have a runtime representation, but will only assert type safety!
- Arrays of inline enum values can now be used as action parameters too. But they will only be represented by their enclosing type for now, i.e. `string`, `number`, etc.
- Foreign keys of projection entities are now propagated as well
## Version 0.14.0 - 2023-12-13

@@ -15,3 +26,3 @@ ### Added

## Version 0.13.0 - 2023-12-06
### Changes
### Changed
- Enums are now generated ecplicitly in the respective _index.js_ files and don't have to extract their values from the model at runtime anymore

@@ -18,0 +29,0 @@

@@ -0,1 +1,3 @@

const { normalise } = require('./identifier')
/**

@@ -32,13 +34,29 @@ * Prints an enum to a buffer. To be precise, it prints

buffer.indent()
const vals = new Set()
for (const [k, v] of kvs) {
buffer.add(`${k}: ${v},`)
vals.add(v?.val ?? v) // in case of wrapped vals we need to unwrap here for the type
buffer.add(`${normalise(k)}: ${v},`)
}
buffer.outdent()
buffer.add('} as const;')
buffer.add(`${opts.export ? 'export ' : ''}type ${name} = ${[...vals].join(' | ')}`)
buffer.add(`${opts.export ? 'export ' : ''}type ${name} = ${stringifyEnumType(kvs)}`)
buffer.add('')
}
/**
* Stringifies a list of enum key-value pairs into the righthand side of a TS type.
* @param {[string, string][]} kvs list of key-value pairs
* @returns {string} a stringified type
* @example
* ```js
* ['A', 'B', 'A'] // -> '"A" | "B"'
* ```
*/
const stringifyEnumType = kvs => [...uniqueValues(kvs)].join(' | ')
/**
* Extracts all unique values from a list of enum key-value pairs.
* If the value is an object, then the `.val` property is used.
* @param {[string, any | {val: any}][]} kvs
*/
const uniqueValues = kvs => new Set(kvs.map(([,v]) => v?.val ?? v)) // in case of wrapped vals we need to unwrap here for the type
// in case of strings, wrap in quotes and fallback to key to make sure values are attached for every key

@@ -108,3 +126,4 @@ const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.stringify(`${value ?? key}`) : value

*/
const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} = { ${kvs.map(([k,v]) => `${k}: ${v}`).join(', ')} }`
// ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} ??= { ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`

@@ -117,3 +136,4 @@

isInlineEnumType,
stringifyEnumImplementation
stringifyEnumImplementation,
stringifyEnumType
}

8

lib/components/inline.js
const { SourceFile, Buffer } = require('../file')
const { normalise } = require('./identifier')
const { docify } = require('./wrappers')

@@ -11,3 +12,2 @@

class InlineDeclarationResolver {
/**

@@ -159,3 +159,3 @@ * @param {string} name

? Object.entries(type.typeInfo.structuredType).map(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
: [`${prefix}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
: [`${normalise(prefix)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
}

@@ -203,3 +203,3 @@

if (type.typeInfo.structuredType) {
const prefix = name ? `${name}${this.getPropertyTypeSeparator()}`: ''
const prefix = name ? `${normalise(name)}${this.getPropertyTypeSeparator()}`: ''
buffer.add(`${prefix} {`)

@@ -213,3 +213,3 @@ buffer.indent()

} else {
buffer.add(`${name}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
buffer.add(`${normalise(name)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
}

@@ -216,0 +216,0 @@ this.printDepth--

@@ -374,9 +374,22 @@ 'use strict'

if (isInlineEnumType(element, this.csn)) {
// we use the singular as the initial declaration of these enums takes place
// while defining the singular class. Which therefore uses the singular over the plural name.
const cleanEntityName = util.singular4(element.parent, true)
const enumName = propertyToInlineEnumName(cleanEntityName, element.name)
result.type = enumName
result.plainName = enumName
result.isInlineDeclaration = true
// element.parent is only set if the enum is attached to an entity's property.
// If it is missing then we are dealing with an inline parameter type of an action.
if (element.parent) {
result.isInlineDeclaration = true
// we use the singular as the initial declaration of these enums takes place
// while defining the singular class. Which therefore uses the singular over the plural name.
const cleanEntityName = util.singular4(element.parent, true)
const enumName = propertyToInlineEnumName(cleanEntityName, element.name)
result.type = enumName
result.plainName = enumName
} else {
// FIXME: this is the case where users have arrays of enums as action parameter type.
// Instead of building the proper type (e.g. `'A' | 'B' | ...`, we are instead building
// the encasing type (e.g. `string` here)
// We should instead aim for a proper type, i.e.
// this.#resolveInlineDeclarationType(element.enum, result, file)
// or
// stringifyEnumType(csnToEnumPairs(element))
this.#resolveTypeName(element.type, result)
}
} else {

@@ -383,0 +396,0 @@ this.resolvePotentialReferenceType(element.type, result, file)

@@ -243,2 +243,2 @@ const annotation = '@odata.draft.enabled'

module.exports = { amendCSN, isView, isUnresolved }
module.exports = { amendCSN, isView, isUnresolved, propagateForeignKeys }

@@ -6,2 +6,3 @@ 'use strict'

const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = require('./components/enum')
const { normalise } = require('./components/identifier')
const path = require('path')

@@ -155,5 +156,5 @@

static stringifyLambda({name, parameters=[], returns='any', initialiser, isStatic=false}) {
const parameterTypes = parameters.map(([n, t]) => `${n}: ${t}`).join(', ')
const parameterTypes = parameters.map(([n, t]) => `${normalise(n)}: ${t}`).join(', ')
const callableSignature = `(${parameterTypes}): ${returns}`
let prefix = name ? `${name}: `: ''
let prefix = name ? `${normalise(name)}: `: ''
if (prefix && isStatic) {

@@ -273,3 +274,3 @@ prefix = `static ${prefix}`

this.enums.data.push({
name: entityFqName,
name: `${entityCleanName}.${propertyName}`,
property: propertyName,

@@ -381,7 +382,7 @@ kvs,

this.inlineEnums.buffer.join(), // needs to be before classes
namespaces.join(),
this.aspects.join(), // needs to be before classes
this.classes.join(),
this.events.buffer.join(),
this.actions.buffer.join()
this.actions.buffer.join(),
namespaces.join() // needs to be after classes for possible declaration merging
].filter(Boolean).join('\n')

@@ -388,0 +389,0 @@ }

@@ -54,2 +54,3 @@ /* eslint-disable indent */

* @returns {string} the name without localisation syntax or untouched.
* @deprecated we have dropped this feature altogether, users specify custom names via @singular/@plural now
*/

@@ -56,0 +57,0 @@ const unlocalize = (name) => {

@@ -5,3 +5,3 @@ 'use strict'

const { amendCSN, isView, isUnresolved } = require('./csn')
const { amendCSN, isView, isUnresolved, propagateForeignKeys } = require('./csn')
// eslint-disable-next-line no-unused-vars

@@ -13,3 +13,3 @@ const { SourceFile, baseDefinitions, Buffer } = require('./file')

const { docify } = require('./components/wrappers')
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType } = require('./components/enum')
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')

@@ -68,2 +68,3 @@ /** @typedef {import('./file').File} File */

amendCSN(csn.xtended)
propagateForeignKeys(csn.inferred)
this.options = { ...defaults, ...options }

@@ -137,2 +138,20 @@ this.logger = logger

/**
* Retrieves all the keys from an entity.
* That is: all keys that are present in both inferred, as well as xtended flavour.
* @returns {[string, object][]} array of key name and key element pairs
*/
#keys(name) {
// FIXME: this is actually pretty bad, as not only have to propagate keys through
// both flavours of CSN (see constructor), but we are now also collecting them from
// both flavours and deduplicating them.
// xtended contains keys that have been inherited from parents
// inferred contains keys from queried entities (thing `entity Foo as select from Bar`, where Bar has keys)
// So we currently need them both.
return Object.entries({
...this.csn.inferred.definitions[name]?.keys ?? {},
...this.csn.xtended.definitions[name]?.keys ?? {}
})
}
/**
* Transforms an entity or CDS aspect into a JS aspect (aka mixin).

@@ -175,3 +194,3 @@ * That is, for an element A we get:

// the containing entity.
for (const [kname, kelement] of Object.entries(this.csn.xtended.definitions[element.target]?.keys ?? {})) {
for (const [kname, kelement] of this.#keys(element.target)) {
if (this.resolver.getMaxCardinality(element) === 1) {

@@ -255,7 +274,9 @@ kelement.isRefNotNull = !!element.notNull || !!element.key

// 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 += '_'

@@ -290,3 +311,3 @@ this.logger.warning(

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

@@ -321,3 +342,3 @@ // PLURAL

name,
this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(this.resolver.resolveAndRequire(type, file)),
this.#stringifyFunctionParamType(type, file)
])

@@ -327,2 +348,8 @@ : []

#stringifyFunctionParamType(type, file) {
return type.enum
? stringifyEnumType(csnToEnumPairs(type))
: this.inlineDeclarationResolver.getPropertyDatatype(this.resolver.resolveAndRequire(type, file))
}
#printFunction(name, func) {

@@ -329,0 +356,0 @@ // FIXME: mostly duplicate of printAction -> reuse

{
"name": "@cap-js/cds-typer",
"version": "0.14.0",
"version": "0.15.0",
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",

@@ -5,0 +5,0 @@ "main": "index.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc