Skip to content

Commit

Permalink
Wrap unusual identifiers in quotes (#134)
Browse files Browse the repository at this point in the history
* Wrap unusual identifiers in quotes

* Add changelog entry

* Handle empty identifiers and add test stubs

* Add test case

* Lint

* Bump version to 0.15.0

* Fix identifier regex
  • Loading branch information
daogrady authored Dec 21, 2023
1 parent 133273d commit 47c079c
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 11 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ All notable changes to this project will be documented in this file.
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
## 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
Expand Down
6 changes: 4 additions & 2 deletions lib/components/enum.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { normalise } = require('./identifier')

/**
* Prints an enum to a buffer. To be precise, it prints
* a constant object and a type which together form an artificial enum.
Expand Down Expand Up @@ -30,7 +32,7 @@ function printEnum(buffer, name, kvs, options = {}) {
buffer.add(`${opts.export ? 'export ' : ''}const ${name} = {`)
buffer.indent()
for (const [k, v] of kvs) {
buffer.add(`${k}: ${v},`)
buffer.add(`${normalise(k)}: ${v},`)
}
buffer.outdent()
buffer.add('} as const;')
Expand Down Expand Up @@ -121,7 +123,7 @@ const isInlineEnumType = (element, csn) => element.enum && !(element.type in csn
* @param {[string, string][]} kvs a list of key-value pairs. Values that are falsey are replaced by
*/
// ??= 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]) => `${k}: ${v}`).join(', ')} }`
const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} ??= { ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`


module.exports = {
Expand Down
15 changes: 15 additions & 0 deletions lib/components/identifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const isValidIdent = /^[_$a-zA-Z][$\w]*$/

/**
* Normalises an identifier to a valid JavaScript identifier.
* I.e. either the identifier itself or a quoted string.
* @param {string} ident the identifier to normalise
* @returns {string} the normalised identifier
*/
const normalise = ident => ident && !isValidIdent.test(ident)
? `"${ident}"`
: ident

module.exports = {
normalise
}
7 changes: 4 additions & 3 deletions lib/components/inline.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { SourceFile, Buffer } = require('../file')
const { normalise } = require('./identifier')
const { docify } = require('./wrappers')

/**
Expand Down Expand Up @@ -154,7 +155,7 @@ class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
flatten(prefix, type) {
return type.typeInfo.structuredType
? 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)}`]
}

printInlineType(name, type, buffer) {
Expand Down Expand Up @@ -197,7 +198,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
this.printDepth++
const lineEnding = this.printDepth > 1 ? ',' : statementEnd
if (type.typeInfo.structuredType) {
const prefix = name ? `${name}${this.getPropertyTypeSeparator()}`: ''
const prefix = name ? `${normalise(name)}${this.getPropertyTypeSeparator()}`: ''
buffer.add(`${prefix} {`)
buffer.indent()
for (const [n, t] of Object.entries(type.typeInfo.structuredType)) {
Expand All @@ -206,7 +207,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
buffer.outdent()
buffer.add(`}${this.getPropertyDatatype(type, '')}${lineEnding}`)
} else {
buffer.add(`${name}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
buffer.add(`${normalise(name)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
}
this.printDepth--
return buffer
Expand Down
5 changes: 3 additions & 2 deletions lib/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const fs = require('fs').promises
const { readFileSync } = require('fs')
const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = require('./components/enum')
const { normalise } = require('./components/identifier')
const path = require('path')

const AUTO_GEN_NOTE = "// This is an automatically generated file. Please do not change its contents manually!"
Expand Down Expand Up @@ -151,9 +152,9 @@ class SourceFile extends File {
* ```
*/
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) {
prefix = `static ${prefix}`
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"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",
"main": "index.js",
"repository": "github:cap-js/cds-typer",
Expand Down
2 changes: 1 addition & 1 deletion test/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class ASTWrapper {
sourceFile.forEachChild(c => {
const slim = visit(c)
// ignore top-level keywords, like 'export', etc.
if (slim?.nodeType !== kinds.Keyword) {
if (slim && slim?.nodeType !== kinds.Keyword) {
this.tree.push(slim)
}
})
Expand Down
29 changes: 29 additions & 0 deletions test/unit/delimident.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'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('enums_test')

describe('Delimited Identifiers', () => {
let astw

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

test('Properties in Aspect Present', () => {
expect(astw.getAspectProperty('_FooAspect', 'sap-icon://a')).toBeTruthy()
const nested = astw.getAspectProperty('_FooAspect', 'sap-icon://b')
expect(nested).toBeTruthy()
expect(nested.type.subtypes[0].members[0].name).toBe('sap-icon://c')
const actions = astw.getAspectProperty('_FooAspect', 'actions')
expect(actions.type.members.find(fn => fn.name === 'sap-icon://f')).toBeTruthy()
})
})
13 changes: 13 additions & 0 deletions test/unit/files/delimident/model.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace delimited_identifiers_test;

entity Foo {
![sap-icon://a]: String;
![sap-icon://b]: {
![sap-icon://c]: String;
};
c: String enum {
![sap-icon://d]
};
} actions {
action ![sap-icon://f]()
}
2 changes: 1 addition & 1 deletion test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ const locations = {
}


const cds2ts = async (cdsFile, options = {}) => await typer.compileFromFile(
const cds2ts = async (cdsFile, options = {}) => typer.compileFromFile(
locations.unit.files(cdsFile),
options
)
Expand Down

0 comments on commit 47c079c

Please sign in to comment.