Skip to content

Commit 80b573f

Browse files
Add ParameterInfo (#124)
* Add ParameterInfo * Better typing for ParameterInfo * Update changelog * Rename counter to count
1 parent d520e4f commit 80b573f

12 files changed

+225
-56
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99
### Fixed
1010
- [.NET] Fix casing in "word" parameter type constant
1111

12+
### Added
13+
- [JavaScript] Add `ParameterInfo` ([#124](https://github.com/cucumber/cucumber-expressions/pull/124))
14+
1215
## [15.1.1] - 2022-04-21
1316
### Fixed
1417
- [JavaScript] Make `CucumberExpression.ast` public (it was accidentally private in 15.1.0)

docs/index.js

+19-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/style.css

+7-3
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,8 @@ select {
445445
background-size: 1.5em 1.5em;
446446
padding-right: 2.5rem;
447447
-webkit-print-color-adjust: exact;
448-
color-adjust: exact;
448+
color-adjust: exact;
449+
print-color-adjust: exact;
449450
}
450451

451452
[multiple] {
@@ -455,7 +456,8 @@ select {
455456
background-size: initial;
456457
padding-right: 0.75rem;
457458
-webkit-print-color-adjust: unset;
458-
color-adjust: unset;
459+
color-adjust: unset;
460+
print-color-adjust: unset;
459461
}
460462

461463
[type='checkbox'],[type='radio'] {
@@ -464,7 +466,8 @@ select {
464466
appearance: none;
465467
padding: 0;
466468
-webkit-print-color-adjust: exact;
467-
color-adjust: exact;
469+
color-adjust: exact;
470+
print-color-adjust: exact;
468471
display: inline-block;
469472
vertical-align: middle;
470473
background-origin: border-box;
@@ -548,6 +551,7 @@ select {
548551
}
549552

550553
[type='file']:focus {
554+
outline: 1px solid ButtonText;
551555
outline: 1px auto -webkit-focus-ring-color;
552556
}
553557

javascript/src/CucumberExpressionGenerator.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,9 @@ export default class CucumberExpressionGenerator {
8686
parameterType: ParameterType<unknown>,
8787
text: string
8888
): ParameterTypeMatcher[] {
89-
// TODO: [].map
90-
const result = []
91-
for (const regexp of parameterType.regexpStrings) {
92-
result.push(new ParameterTypeMatcher(parameterType, regexp, text))
93-
}
94-
return result
89+
return parameterType.regexpStrings.map(
90+
(regexp) => new ParameterTypeMatcher(parameterType, regexp, text)
91+
)
9592
}
9693
}
9794

javascript/src/GeneratedExpression.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ParameterType from './ParameterType.js'
2+
import { ParameterInfo } from './types.js'
23

34
export default class GeneratedExpression {
45
constructor(
@@ -16,17 +17,43 @@ export default class GeneratedExpression {
1617
* @returns {ReadonlyArray.<String>}
1718
*/
1819
get parameterNames(): readonly string[] {
20+
return this.parameterInfos.map((i) => `${i.name}${i.count === 1 ? '' : i.count.toString()}`)
21+
}
22+
23+
/**
24+
* Returns an array of ParameterInfo to use in generated function/method signatures
25+
*/
26+
get parameterInfos(): readonly ParameterInfo[] {
1927
const usageByTypeName: { [key: string]: number } = {}
20-
return this.parameterTypes.map((t) => getParameterName(t.name || '', usageByTypeName))
28+
return this.parameterTypes.map((t) => getParameterInfo(t, usageByTypeName))
2129
}
2230
}
2331

24-
function getParameterName(typeName: string, usageByTypeName: { [key: string]: number }) {
25-
let count = usageByTypeName[typeName]
26-
count = count ? count + 1 : 1
27-
usageByTypeName[typeName] = count
28-
29-
return count === 1 ? typeName : `${typeName}${count}`
32+
function getParameterInfo(
33+
parameterType: ParameterType<unknown>,
34+
usageByName: { [key: string]: number }
35+
): ParameterInfo {
36+
const name = parameterType.name || ''
37+
let counter = usageByName[name]
38+
counter = counter ? counter + 1 : 1
39+
usageByName[name] = counter
40+
let type: string | null
41+
if (parameterType.type) {
42+
if (typeof parameterType.type === 'string') {
43+
type = parameterType.type
44+
} else if ('name' in parameterType.type) {
45+
type = parameterType.type.name
46+
} else {
47+
type = null
48+
}
49+
} else {
50+
type = null
51+
}
52+
return {
53+
type,
54+
name,
55+
count: counter,
56+
}
3057
}
3158

3259
function format(pattern: string, ...args: readonly string[]): string {

javascript/src/ParameterType.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ import CucumberExpressionError from './CucumberExpressionError.js'
33
const ILLEGAL_PARAMETER_NAME_PATTERN = /([[\]()$.|?*+])/
44
const UNESCAPE_PATTERN = () => /(\\([[$.|?*+\]]))/g
55

6+
interface Constructor<T> extends Function {
7+
new (...args: unknown[]): T
8+
prototype: T
9+
}
10+
11+
type Factory<T> = (...args: unknown[]) => T
12+
613
export default class ParameterType<T> {
7-
private transformFn: (...match: readonly string[]) => T
14+
private transformFn: (...match: readonly string[]) => T | PromiseLike<T>
815

916
public static compare(pt1: ParameterType<unknown>, pt2: ParameterType<unknown>) {
1017
if (pt1.preferForRegexpMatch && !pt2.preferForRegexpMatch) {
@@ -42,8 +49,8 @@ export default class ParameterType<T> {
4249
constructor(
4350
public readonly name: string | undefined,
4451
regexps: readonly RegExp[] | readonly string[] | RegExp | string,
45-
private readonly type: unknown,
46-
transform: (...match: string[]) => T,
52+
public readonly type: Constructor<T> | Factory<T> | string | null,
53+
transform: (...match: string[]) => T | PromiseLike<T>,
4754
public readonly useForSnippets: boolean,
4855
public readonly preferForRegexpMatch: boolean
4956
) {

javascript/src/defineDefaultParameterTypes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default function defineDefaultParameterTypes(registry: DefinesParameterTy
6060
new ParameterType(
6161
'bigdecimal',
6262
FLOAT_REGEXP,
63-
Number,
63+
String,
6464
(s) => (s === undefined ? null : s),
6565
false,
6666
false
@@ -104,7 +104,7 @@ export default function defineDefaultParameterTypes(registry: DefinesParameterTy
104104
new ParameterType(
105105
'biginteger',
106106
INTEGER_REGEXPS,
107-
Number,
107+
BigInt,
108108
(s) => (s === undefined ? null : BigInt(s)),
109109
false,
110110
false

javascript/src/types.ts

+15
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,18 @@ export interface Expression {
99
readonly source: string
1010
match(text: string): readonly Argument[] | null
1111
}
12+
13+
export type ParameterInfo = {
14+
/**
15+
* The string representation of the original ParameterType#type property
16+
*/
17+
type: string | null
18+
/**
19+
* The parameter type name
20+
*/
21+
name: string
22+
/**
23+
* The number of times this name has been used so far
24+
*/
25+
count: number
26+
}

javascript/test/CucumberExpressionGeneratorTest.ts

+106-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import CucumberExpression from '../src/CucumberExpression.js'
44
import CucumberExpressionGenerator from '../src/CucumberExpressionGenerator.js'
55
import ParameterType from '../src/ParameterType.js'
66
import ParameterTypeRegistry from '../src/ParameterTypeRegistry.js'
7+
import { ParameterInfo } from '../src/types.js'
78

89
class Currency {
910
constructor(public readonly s: string) {}
@@ -15,11 +16,11 @@ describe('CucumberExpressionGenerator', () => {
1516

1617
function assertExpression(
1718
expectedExpression: string,
18-
expectedArgumentNames: string[],
19+
expectedParameterInfo: ParameterInfo[],
1920
text: string
2021
) {
2122
const generatedExpression = generator.generateExpressions(text)[0]
22-
assert.deepStrictEqual(generatedExpression.parameterNames, expectedArgumentNames)
23+
assert.deepStrictEqual(generatedExpression.parameterInfos, expectedParameterInfo)
2324
assert.strictEqual(generatedExpression.source, expectedExpression)
2425

2526
const cucumberExpression = new CucumberExpression(
@@ -32,7 +33,7 @@ describe('CucumberExpressionGenerator', () => {
3233
`Expected text '${text}' to match generated expression '${generatedExpression.source}'`
3334
)
3435
}
35-
assert.strictEqual(match.length, expectedArgumentNames.length)
36+
assert.strictEqual(match.length, expectedParameterInfo.length)
3637
}
3738

3839
beforeEach(() => {
@@ -63,37 +64,110 @@ describe('CucumberExpressionGenerator', () => {
6364
})
6465

6566
it('generates expression with escaped slashes', () => {
66-
assertExpression('The {int}\\/{int}\\/{int} hey', ['int', 'int2', 'int3'], 'The 1814/05/17 hey')
67+
assertExpression(
68+
'The {int}\\/{int}\\/{int} hey',
69+
[
70+
{
71+
type: 'Number',
72+
name: 'int',
73+
count: 1,
74+
},
75+
{
76+
type: 'Number',
77+
name: 'int',
78+
count: 2,
79+
},
80+
{
81+
type: 'Number',
82+
name: 'int',
83+
count: 3,
84+
},
85+
],
86+
'The 1814/05/17 hey'
87+
)
6788
})
6889

6990
it('generates expression for int float arg', () => {
7091
assertExpression(
7192
'I have {int} cukes and {float} euro',
72-
['int', 'float'],
93+
[
94+
{
95+
type: 'Number',
96+
name: 'int',
97+
count: 1,
98+
},
99+
{
100+
type: 'Number',
101+
name: 'float',
102+
count: 1,
103+
},
104+
],
73105
'I have 2 cukes and 1.5 euro'
74106
)
75107
})
76108

77109
it('generates expression for strings', () => {
78110
assertExpression(
79111
'I like {string} and {string}',
80-
['string', 'string2'],
112+
[
113+
{
114+
type: 'String',
115+
name: 'string',
116+
count: 1,
117+
},
118+
{
119+
type: 'String',
120+
name: 'string',
121+
count: 2,
122+
},
123+
],
81124
'I like "bangers" and \'mash\''
82125
)
83126
})
84127

85128
it('generates expression with % sign', () => {
86-
assertExpression('I am {int}%% foobar', ['int'], 'I am 20%% foobar')
129+
assertExpression(
130+
'I am {int}%% foobar',
131+
[
132+
{
133+
type: 'Number',
134+
name: 'int',
135+
count: 1,
136+
},
137+
],
138+
'I am 20%% foobar'
139+
)
87140
})
88141

89142
it('generates expression for just int', () => {
90-
assertExpression('{int}', ['int'], '99999')
143+
assertExpression(
144+
'{int}',
145+
[
146+
{
147+
type: 'Number',
148+
name: 'int',
149+
count: 1,
150+
},
151+
],
152+
'99999'
153+
)
91154
})
92155

93156
it('numbers only second argument when builtin type is not reserved keyword', () => {
94157
assertExpression(
95158
'I have {float} cukes and {float} euro',
96-
['float', 'float2'],
159+
[
160+
{
161+
type: 'Number',
162+
name: 'float',
163+
count: 1,
164+
},
165+
{
166+
type: 'Number',
167+
name: 'float',
168+
count: 2,
169+
},
170+
],
97171
'I have 2.5 cukes and 1.5 euro'
98172
)
99173
})
@@ -103,18 +177,38 @@ describe('CucumberExpressionGenerator', () => {
103177
new ParameterType('currency', /[A-Z]{3}/, Currency, (s) => new Currency(s), true, false)
104178
)
105179

106-
assertExpression('I have a {currency} account', ['currency'], 'I have a EUR account')
180+
assertExpression(
181+
'I have a {currency} account',
182+
[
183+
{
184+
type: 'Currency',
185+
name: 'currency',
186+
count: 1,
187+
},
188+
],
189+
'I have a EUR account'
190+
)
107191
})
108192

109193
it('prefers leftmost match when there is overlap', () => {
110194
parameterTypeRegistry.defineParameterType(
111-
new ParameterType<Currency>('currency', /c d/, Currency, (s) => new Currency(s), true, false)
195+
new ParameterType('currency', /c d/, Currency, (s) => new Currency(s), true, false)
112196
)
113197
parameterTypeRegistry.defineParameterType(
114198
new ParameterType('date', /b c/, Date, (s) => new Date(s), true, false)
115199
)
116200

117-
assertExpression('a {date} d e f g', ['date'], 'a b c d e f g')
201+
assertExpression(
202+
'a {date} d e f g',
203+
[
204+
{
205+
type: 'Date',
206+
name: 'date',
207+
count: 1,
208+
},
209+
],
210+
'a b c d e f g'
211+
)
118212
})
119213

120214
// TODO: prefers widest match

0 commit comments

Comments
 (0)