cddl 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ import { describe, it, vi, expect } from 'vitest'
2
+ import repl from 'node:repl'
3
+ import { builder, handler, evaluate } from '../../src/cli/commands/repl.js'
4
+ import { Tokens } from '../../src/tokens.js'
5
+
6
+ describe('repl command', () => {
7
+ it('builder', () => {
8
+ const yargs: any = {}
9
+ yargs.epilogue = vi.fn().mockReturnValue(yargs)
10
+ yargs.help = vi.fn().mockReturnValue(yargs)
11
+ builder(yargs)
12
+
13
+ expect(yargs.epilogue).toHaveBeenCalledTimes(1)
14
+ expect(yargs.help).toHaveBeenCalledTimes(1)
15
+ })
16
+
17
+ describe('handler', () => {
18
+ it('starts repl', () => {
19
+ const startSpy = vi.spyOn(repl, 'start').mockReturnValue({} as any)
20
+ handler()
21
+ expect(startSpy).toHaveBeenCalledWith(expect.objectContaining({
22
+ prompt: '> ',
23
+ eval: evaluate
24
+ }))
25
+ startSpy.mockRestore()
26
+ })
27
+ })
28
+
29
+ describe('evaluate', () => {
30
+ it('returns error if no input', () => {
31
+ const cb = vi.fn()
32
+ evaluate('', {}, '', cb)
33
+ expect(cb).toHaveBeenCalledWith(expect.any(Error), null)
34
+ })
35
+
36
+ it('tokenizes input using Lexer', () => {
37
+ const cb = vi.fn()
38
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
39
+
40
+ evaluate('foo = 1', {}, '', cb)
41
+
42
+ expect(consoleSpy).toHaveBeenCalledTimes(3) // foo, =, 1
43
+ expect(consoleSpy).toHaveBeenNthCalledWith(1, expect.objectContaining({ Type: Tokens.IDENT, Literal: 'foo' }))
44
+ expect(consoleSpy).toHaveBeenNthCalledWith(2, expect.objectContaining({ Type: Tokens.ASSIGN, Literal: '=' }))
45
+ expect(consoleSpy).toHaveBeenNthCalledWith(3, expect.objectContaining({ Type: Tokens.NUMBER, Literal: '1' }))
46
+
47
+ expect(cb).toHaveBeenCalledWith(null, null)
48
+
49
+ consoleSpy.mockRestore()
50
+ })
51
+ })
52
+ })
@@ -0,0 +1,66 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, vi, expect, beforeEach, afterAll } from 'vitest'
4
+ import { builder, handler } from '../../src/cli/commands/validate.js'
5
+
6
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
7
+ const validCDDL = path.join(__dirname, '..', '..', '..', '..', 'examples', 'webdriver', 'local.cddl')
8
+ const buggyCDDL = path.join(__dirname, '..', '..', '..', '..', 'examples', 'commons', 'buggy.cddl')
9
+
10
+ describe('validate command', () => {
11
+ it('builder', () => {
12
+ const yargs: any = {}
13
+ yargs.epilogue = vi.fn().mockReturnValue(yargs)
14
+ yargs.help = vi.fn().mockReturnValue(yargs)
15
+ builder(yargs)
16
+
17
+ expect(yargs.epilogue).toBeCalledTimes(1)
18
+ expect(yargs.help).toBeCalledTimes(1)
19
+ })
20
+
21
+ describe('handler', () => {
22
+ const consoleLogOrig = console.log.bind(console)
23
+ const consoleErrorOrig = console.error.bind(console)
24
+ const processExitOrig = process.exit.bind(process)
25
+
26
+ beforeEach(() => {
27
+ // @ts-expect-error
28
+ process.exit = vi.fn()
29
+ console.log = vi.fn()
30
+ console.error = vi.fn()
31
+ })
32
+
33
+ afterAll(() => {
34
+ process.exit = processExitOrig
35
+ console.log = consoleLogOrig
36
+ console.error = consoleErrorOrig
37
+ })
38
+
39
+ it('fails if file does not exist', () => {
40
+ handler({ filePath: '/foo/bar', _: [], $0: '' })
41
+ expect(process.exit).toBeCalledTimes(1)
42
+ expect(console.error).toBeCalledTimes(1)
43
+ expect(process.exit).toBeCalledWith(1)
44
+ })
45
+
46
+ it('fails if cddl file is buggy', () => {
47
+ handler({ filePath: buggyCDDL, _: [], $0: '' })
48
+ expect(process.exit).toBeCalledTimes(1)
49
+ expect(console.error).toBeCalledTimes(1)
50
+ expect(process.exit).toBeCalledWith(1)
51
+ })
52
+
53
+ it('passes if cddl file is valid', () => {
54
+ handler({ filePath: validCDDL, _: [], $0: '' })
55
+ expect(process.exit).toBeCalledTimes(0)
56
+ expect(console.log).toBeCalledTimes(1)
57
+ })
58
+
59
+ it('handles relative paths', () => {
60
+ handler({ filePath: 'relative/path/to/file.cddl', _: [], $0: '' })
61
+ expect(process.exit).toBeCalledTimes(1)
62
+ expect(console.error).toBeCalledTimes(1)
63
+ expect(process.exit).toBeCalledWith(1)
64
+ })
65
+ })
66
+ })
@@ -0,0 +1,18 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import Parser from '../src/parser.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+
9
+ describe('complex types', () => {
10
+ it('can parse complex types correctly', () => {
11
+ const p = new Parser(path.join(__dirname, '..', '..', '..', 'examples', 'commons', 'complex_types.cddl'))
12
+ const ast = p.parse()
13
+ const localValue = ast.find((item: any) => item.Name === 'LocalValue')
14
+
15
+ expect(localValue).toBeDefined()
16
+ expect(localValue).toMatchSnapshot()
17
+ })
18
+ })
@@ -0,0 +1,25 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import Parser from '../src/parser.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+
9
+ describe('examples', () => {
10
+ describe('common', () => {
11
+ const testCases: { name: string, fixture: string }[] = [
12
+ { name: 'can parse group choice', fixture: 'group_choice.cddl' },
13
+ { name: 'can parse literals', fixture: 'literals.cddl' },
14
+ { name: 'can parse mixin union', fixture: 'mixin_union.cddl' },
15
+ { name: 'can parse named group choice', fixture: 'named_group_choice.cddl' }
16
+ ]
17
+
18
+ for (const { name, fixture } of testCases) {
19
+ it(name, async () => {
20
+ const p = new Parser(path.join(__dirname, '..', '..', '..', 'examples', 'commons', fixture))
21
+ expect(p.parse()).toMatchSnapshot()
22
+ })
23
+ }
24
+ })
25
+ })
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import Parser from '../src/parser.js'
3
+ import * as fs from 'node:fs'
4
+
5
+ vi.mock('node:fs')
6
+
7
+ describe('Group Choice Parsing', () => {
8
+ const parse = (cddl: string) => {
9
+ vi.mocked(fs.readFileSync).mockReturnValue(cddl)
10
+ const parser = new Parser('dummy.cddl')
11
+ return parser.parse()
12
+ }
13
+
14
+ beforeEach(() => {
15
+ vi.clearAllMocks()
16
+ })
17
+
18
+ describe('Group Choice (//)', () => {
19
+ it('should verify group choice operator //', () => {
20
+ const cddl = `
21
+ mygroup = (int // text)
22
+ `
23
+ const ast = parse(cddl)
24
+ expect(ast).toMatchSnapshot()
25
+ })
26
+
27
+ it('should throw error when group choice operator // is at start of group', () => {
28
+ const cddl = `
29
+ mygroup = (// int)
30
+ `
31
+ expect(() => parse(cddl)).toThrowError('Unexpected group choice operator "//" at start of group')
32
+ })
33
+
34
+ it('should parse nested group choice', () => {
35
+ const cddl = `
36
+ mygroup = ((int // text))
37
+ `
38
+ const ast = parse(cddl)
39
+ expect(ast).toMatchSnapshot()
40
+ })
41
+
42
+ it('should parse group choice with multiple items', () => {
43
+ const cddl = `
44
+ mygroup = (int, text // float)
45
+ `
46
+ const ast = parse(cddl)
47
+ expect(ast).toMatchSnapshot()
48
+ })
49
+ })
50
+
51
+ describe('Map Group Choice', () => {
52
+ it('should parse group choice inside map', () => {
53
+ const cddl = `
54
+ mymap = {
55
+ "a" => 1 //
56
+ "b" => 2
57
+ }
58
+ `
59
+ const ast = parse(cddl)
60
+ expect(ast).toMatchSnapshot()
61
+ })
62
+ })
63
+
64
+ describe('Type Choice (/)', () => {
65
+ it('should parse type choice inside group', () => {
66
+ const cddl = `
67
+ mygroup = (int / text)
68
+ `
69
+ const ast = parse(cddl)
70
+ expect(ast).toMatchSnapshot()
71
+ })
72
+
73
+ // This tests the change: closingTokens.includes(Tokens.RPAREN) && this.peekToken.Type === Tokens.SLASH
74
+ it('should correctly handle slash in mixed context', () => {
75
+ const cddl = `
76
+ myrule = [ (int / text) ]
77
+ `
78
+ const ast = parse(cddl)
79
+ expect(ast).toMatchSnapshot()
80
+ })
81
+ })
82
+
83
+ describe('Blocks with Braces', () => {
84
+ // Covers changes in openSegment and parseAssignmentValue regarding Tokens.LBRACE
85
+ it('should parse nested group inside map', () => {
86
+ // This relates to the removal of special LPAREN handling inside LBRACE in openSegment
87
+ const cddl = `
88
+ myrule = { (int) }
89
+ `
90
+ const ast = parse(cddl)
91
+ // Expecting parsing to succeed and structure to be correct
92
+ expect(ast).toMatchSnapshot()
93
+ })
94
+
95
+ it('should parse bare group inside brace', () => {
96
+ const cddl = `
97
+ myrule = { int }
98
+ `
99
+ const ast = parse(cddl)
100
+ expect(ast).toMatchSnapshot()
101
+ })
102
+ })
103
+ })
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { Tokens } from '../src/tokens.js'
4
+ import Lexer from '../src/lexer.js'
5
+
6
+ describe('lexer', () => {
7
+ it('should allow to read token', () => {
8
+ const input = '=+(){},/<>'
9
+ const tests = [
10
+ [Tokens.ASSIGN, '='],
11
+ [Tokens.PLUS, '+'],
12
+ [Tokens.LPAREN, '('],
13
+ [Tokens.RPAREN, ')'],
14
+ [Tokens.LBRACE, '{'],
15
+ [Tokens.RBRACE, '}'],
16
+ [Tokens.COMMA, ','],
17
+ [Tokens.SLASH, '/'],
18
+ [Tokens.LT, '<'],
19
+ [Tokens.GT, '>']
20
+ ]
21
+
22
+ const l = new Lexer(input)
23
+ for (const [Type, Literal] of tests) {
24
+ const token = l.nextToken()
25
+ expect(token.Type).toBe(Type)
26
+ expect(token.Literal).toBe(Literal)
27
+ }
28
+ })
29
+
30
+ it('should read identifiers and comments', () => {
31
+ const input = ' headers, ; Headers for the recipient'
32
+ const tests = [
33
+ [Tokens.IDENT, 'headers'],
34
+ [Tokens.COMMA, ','],
35
+ [Tokens.COMMENT, '; Headers for the recipient']
36
+ ]
37
+
38
+ const l = new Lexer(input)
39
+ for (const [Type, Literal] of tests) {
40
+ const token = l.nextToken()
41
+ expect(token.Type).toBe(Type)
42
+ expect(token.Literal).toBe(Literal)
43
+ }
44
+ })
45
+
46
+ it('should correctly report location info', () => {
47
+ const input = 'a = 1'
48
+ const l = new Lexer(input)
49
+
50
+ // consume tokens until EOF
51
+ while (l.nextToken().Type !== 'EOF') {}
52
+
53
+ // consume more EOFs to push position further
54
+ for (let i = 0; i < 10; ++i) {
55
+ l.nextToken()
56
+ }
57
+
58
+ // This should trigger the fallback
59
+ const locEnd = l.getLocation()
60
+ expect(locEnd.line).toBe(0)
61
+ expect(locEnd.position).toBe(0)
62
+ })
63
+ })
@@ -0,0 +1,21 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import CDDL from '../src/index.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+ const validCDDL = path.join(__dirname, '..', '..', '..', 'examples', 'commons', 'arrays.cddl')
9
+ const buggyCDDL = path.join(__dirname, '..', '..', '..', 'examples', 'commons', 'buggy.cddl')
10
+
11
+ describe('validate', () => {
12
+ it('should return a json in success case', () => {
13
+ const ast = CDDL.parse(validCDDL)
14
+ expect(typeof ast).toBe('object')
15
+ })
16
+
17
+ it('should throw an error if invalud', () => {
18
+ expect(() => CDDL.parse(buggyCDDL))
19
+ .toThrow()
20
+ })
21
+ })
@@ -0,0 +1,44 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import fs from 'node:fs'
4
+ import { describe, it, expect, vi } from 'vitest'
5
+
6
+ import Parser from '../src/parser.js'
7
+
8
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
9
+
10
+ describe('parser', () => {
11
+ const testCases: { name: string, fixture: string }[] = [
12
+ { name: 'should correctly parse CDDL file', fixture: 'example.cddl' },
13
+ { name: 'can parse compositions', fixture: 'compositions.cddl' },
14
+ { name: 'can parse ranges', fixture: 'ranges.cddl' },
15
+ { name: 'can parse occurrences', fixture: 'occurrences.cddl' },
16
+ { name: 'can parse arrays', fixture: 'arrays.cddl' },
17
+ { name: 'can parse unwrapped arrays', fixture: 'unwrapping.cddl' },
18
+ { name: 'can parse comments', fixture: 'comments.cddl' },
19
+ { name: 'can parse choices', fixture: 'choices.cddl' },
20
+ { name: 'can parse nested groups', fixture: 'nested.cddl' },
21
+ { name: 'can parse operators', fixture: 'operators.cddl' }
22
+ ]
23
+
24
+ for (const { name, fixture } of testCases) {
25
+ it(name, async () => {
26
+ const p = new Parser(path.join(__dirname, '..', '..', '..', 'examples', 'commons', fixture))
27
+ expect(p.parse()).toMatchSnapshot()
28
+ })
29
+ }
30
+
31
+ it('throws if group identifier is missing', () => {
32
+ vi.spyOn(fs, 'readFileSync').mockReturnValue('=')
33
+ const p = new Parser('foo.cddl')
34
+ expect(() => p.parse()).toThrow('group identifier expected')
35
+ vi.restoreAllMocks()
36
+ })
37
+
38
+ it('throws if assignment operator is missing', () => {
39
+ vi.spyOn(fs, 'readFileSync').mockReturnValue('groupName bar')
40
+ const p = new Parser('foo.cddl')
41
+ expect(() => p.parse()).toThrow('group identifier expected')
42
+ vi.restoreAllMocks()
43
+ })
44
+ })
@@ -0,0 +1,15 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import Parser from '../src/parser.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+
9
+ describe('webdriver local cddl', () => {
10
+ it('can parse local.cddl', () => {
11
+ const filePath = path.join(__dirname, '..', '..', '..', 'examples', 'webdriver', 'local.cddl')
12
+ const p = new Parser(filePath)
13
+ expect(p.parse()).toMatchSnapshot()
14
+ })
15
+ })
@@ -0,0 +1,15 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import Parser from '../src/parser.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+
9
+ describe('webdriver remote cddl', () => {
10
+ it('can parse remote.cddl', () => {
11
+ const filePath = path.join(__dirname, '..', '..', '..', 'examples', 'webdriver', 'remote.cddl')
12
+ const p = new Parser(filePath)
13
+ expect(p.parse()).toMatchSnapshot()
14
+ })
15
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "rootDir": "src",
6
+ },
7
+ "types": ["node"],
8
+ "include": [
9
+ "src/**/*",
10
+ ]
11
+ }