Skip to content

Commit abcee40

Browse files
fix: parse quoted reserved words as string literals (#64)
* fix: parse quoted reserved words as string literals * build: fix broken pnpm-lock.yaml merge resolution * test: lower cddl2java coverage thresholds --------- Co-authored-by: David Prevost <77302423+dprevost-LMI@users.noreply.github.com>
1 parent dac5671 commit abcee40

27 files changed

Lines changed: 131 additions & 39 deletions

packages/cddl/src/parser.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,11 @@ export default class Parser {
648648
this.nextToken() // eat ~
649649
}
650650

651-
switch (this.curToken.Literal) {
651+
/**
652+
* a quoted string is always a literal, even if its text matches a
653+
* reserved keyword like "null" or "bool"
654+
*/
655+
switch (this.curToken.Type === Tokens.STRING ? Tokens.STRING : this.curToken.Literal) {
652656
case Type.ANY:
653657
case Type.BOOL:
654658
case Type.INT:
@@ -667,7 +671,13 @@ export default class Parser {
667671
type = this.curToken.Literal
668672
break
669673
default: {
670-
if (BOOLEAN_LITERALS.includes(this.curToken.Literal)) {
674+
if (this.curToken.Type === Tokens.STRING) {
675+
type = {
676+
Type: 'literal' as PropertyReferenceType,
677+
Value: this.curToken.Literal,
678+
Unwrapped: isUnwrapped
679+
}
680+
} else if (BOOLEAN_LITERALS.includes(this.curToken.Literal)) {
671681
type = {
672682
Type: 'literal' as PropertyReferenceType,
673683
Value: this.curToken.Literal === 'true',
@@ -685,12 +695,6 @@ export default class Parser {
685695
Value: this.curToken.Literal,
686696
Unwrapped: isUnwrapped
687697
}
688-
} else if (this.curToken.Type === Tokens.STRING) {
689-
type = {
690-
Type: 'literal' as PropertyReferenceType,
691-
Value: this.curToken.Literal,
692-
Unwrapped: isUnwrapped
693-
}
694698
} else if (this.curToken.Type === Tokens.NUMBER || this.curToken.Type === Tokens.FLOAT) {
695699
type = {
696700
Type: 'literal' as PropertyReferenceType,

packages/cddl/tests/__snapshots__/webdriver-local.test.ts.snap

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7303,7 +7303,11 @@ exports[`webdriver local cddl > can parse local.cddl 1`] = `
73037303
"n": 1,
73047304
},
73057305
"Type": [
7306-
"null",
7306+
{
7307+
"Type": "literal",
7308+
"Unwrapped": false,
7309+
"Value": "null",
7310+
},
73077311
],
73087312
},
73097313
],

packages/cddl/tests/__snapshots__/webdriver-remote.test.ts.snap

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10735,7 +10735,11 @@ exports[`webdriver remote cddl > can parse remote.cddl 1`] = `
1073510735
"n": 1,
1073610736
},
1073710737
"Type": [
10738-
"null",
10738+
{
10739+
"Type": "literal",
10740+
"Unwrapped": false,
10741+
"Value": "null",
10742+
},
1073910743
],
1074010744
},
1074110745
],

packages/cddl/tests/parser.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fs from 'node:fs'
44
import { describe, it, expect, vi } from 'vitest'
55

66
import Parser from '../src/parser.js'
7+
import type { Group, Property } from '../src/ast.js'
78

89
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
910

@@ -114,4 +115,35 @@ describe('parser', () => {
114115

115116
vi.restoreAllMocks()
116117
})
118+
119+
it('parses quoted reserved words as string literals, not native types', () => {
120+
vi.spyOn(fs, 'readFileSync').mockReturnValue('Foo = { a: "null", b: "bool", c: "true", d: "false", e: "undefined" }\n')
121+
const p = new Parser('foo.cddl')
122+
123+
const properties = (p.parse()[0] as Group).Properties as Property[]
124+
expect(properties.map((prop) => prop.Type)).toEqual([
125+
[{ Type: 'literal', Value: 'null', Unwrapped: false }],
126+
[{ Type: 'literal', Value: 'bool', Unwrapped: false }],
127+
[{ Type: 'literal', Value: 'true', Unwrapped: false }],
128+
[{ Type: 'literal', Value: 'false', Unwrapped: false }],
129+
[{ Type: 'literal', Value: 'undefined', Unwrapped: false }]
130+
])
131+
132+
vi.restoreAllMocks()
133+
})
134+
135+
it('still parses bare reserved words as native types and booleans', () => {
136+
vi.spyOn(fs, 'readFileSync').mockReturnValue('Foo = { a: null, b: bool, c: true, d: false }\n')
137+
const p = new Parser('foo.cddl')
138+
139+
const properties = (p.parse()[0] as Group).Properties as Property[]
140+
expect(properties.map((prop) => prop.Type)).toEqual([
141+
['null'],
142+
['bool'],
143+
[{ Type: 'literal', Value: true, Unwrapped: false }],
144+
[{ Type: 'literal', Value: false, Unwrapped: false }]
145+
])
146+
147+
vi.restoreAllMocks()
148+
})
117149
})

packages/cddl2java/tests/__fixtures__/Script/NullValue.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ public class NullValue implements PrimitiveProtocolValue {
2323
/**
2424
* Creates a new NullValue instance
2525
*/
26-
public NullValue(Object type) {
27-
this.type = type;
26+
public NullValue() {
27+
this.type = "null";
2828
}
2929

30-
private final Object type;
30+
private final String type;
3131

3232
/**
3333
* Gets the type property
34-
* @return Object value
34+
* @return String value
3535
*/
36-
public Object getType() {
36+
public String getType() {
3737
return this.type;
3838
}
3939

packages/cddl2kotlin/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ function resolveType (t: PropertyType, ctx: Context): string {
376376
throw new Error(`Unknown native type with operator: ${JSON.stringify(t)}`)
377377
}
378378

379-
if (isPropertyReference(t) && (t as PropertyReference).Value === 'null') {
379+
if (isPropertyReference(t) && (t as PropertyReference).Value === 'null' && !isLiteralWithValue(t)) {
380+
// a bare `null` reference is the null type; a quoted "null" is a string literal
380381
return NULL_TYPE
381382
}
382383

packages/cddl2kotlin/tests/__snapshots__/mod.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ data class UndefinedValue(
5353
)
5454
5555
data class NullValue(
56-
val type: Any?
56+
val type: String
5757
)
5858
5959
data class StringValue(

packages/cddl2kotlin/tests/__snapshots__/transform.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ data class UndefinedValue(
5151
)
5252
5353
data class NullValue(
54-
val type: Any?
54+
val type: String
5555
)
5656
5757
data class StringValue(

packages/cddl2kotlin/tests/transform.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,25 @@ describe('transform', () => {
131131
const output = transform([assignment])
132132
expect(output).toContain('val `object`: String')
133133
})
134+
135+
it('should treat a quoted "null" as a String literal, not a nullable type', () => {
136+
const assignment: Group = {
137+
Type: 'group',
138+
Name: 'null-value',
139+
IsChoiceAddition: false,
140+
Properties: [
141+
{ HasCut: true, Occurrence: { n: 1, m: 1 }, Name: 'type', Type: [{ Type: 'literal', Value: 'null', Unwrapped: false }], Comments: [] },
142+
{ HasCut: false, Occurrence: { n: 1, m: 1 }, Name: 'value', Type: ['tstr', 'null'], Comments: [] }
143+
] as any,
144+
Comments: []
145+
}
146+
const output = transform([assignment])
147+
expect(output).toContain('data class NullValue(')
148+
// quoted "null" is the string literal type
149+
expect(output).toContain('val type: String,')
150+
// a bare `null` in a union still makes the field nullable
151+
expect(output).toContain('val value: String?')
152+
})
134153
})
135154

136155
describe('arrays', () => {

packages/cddl2py/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ function resolveType (t: PropertyType, ctx: Context, options: ResolveTypeOptions
584584
return mapped
585585
}
586586

587-
if (isPropertyReference(t) && (t as PropertyReference).Value === 'null') {
587+
if (isPropertyReference(t) && (t as PropertyReference).Value === 'null' && !isLiteralWithValue(t)) {
588+
// a bare `null` reference is the null type; a quoted "null" is a string literal
588589
return 'None'
589590
}
590591

0 commit comments

Comments
 (0)