From 110b1ec1a1e86d22200b16921c26bbab7406ac5c Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Mon, 5 Mar 2018 10:53:27 -0800 Subject: [PATCH 01/26] wip --- cucumber-expressions/go/argument.go | 53 +++++++++ .../go/cucumber_expression.go | 83 +++++++++++++ cucumber-expressions/go/parameter_type.go | 109 ++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 cucumber-expressions/go/argument.go create mode 100644 cucumber-expressions/go/cucumber_expression.go create mode 100644 cucumber-expressions/go/parameter_type.go diff --git a/cucumber-expressions/go/argument.go b/cucumber-expressions/go/argument.go new file mode 100644 index 0000000000..edc9545184 --- /dev/null +++ b/cucumber-expressions/go/argument.go @@ -0,0 +1,53 @@ +package cucumberexpressions + +type Argument struct { + group string + parameterType string +} + +// +// const { CucumberExpressionError } = require('./errors') +// +// class Argument { +// static build(treeRegexp, text, parameterTypes) { +// const group = treeRegexp.match(text) +// if (!group) return null +// +// const argGroups = group.children +// +// if (argGroups.length !== parameterTypes.length) { +// throw new CucumberExpressionError( +// `Expression ${treeRegexp.regexp} has ${ +// argGroups.length +// } capture groups (${argGroups.map(g => g.value)}), but there were ${ +// parameterTypes.length +// } parameter types (${parameterTypes.map(p => p.name)})` +// ) +// } +// +// return parameterTypes.map( +// (parameterType, i) => new Argument(argGroups[i], parameterType) +// ) +// } +// +// constructor(group, parameterType) { +// this._group = group +// this._parameterType = parameterType +// } +// +// get group() { +// return this._group +// } +// +// /** +// * Get the value returned by the parameter type's transformer function. +// * +// * @param thisObj the object in which the transformer function is applied. +// */ +// getValue(thisObj) { +// let groupValues = this._group ? this._group.values : null +// return this._parameterType.transform(thisObj, groupValues) +// } +// } +// +// module.exports = Argument diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go new file mode 100644 index 0000000000..5dbc5a7d38 --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression.go @@ -0,0 +1,83 @@ +package cucumberexpressions + +// const Argument = require('./argument') +// const TreeRegexp = require('./tree_regexp') +// const { UndefinedParameterTypeError } = require('./errors') +// +// class CucumberExpression { +// /** +// * @param expression +// * @param parameterTypeRegistry +// */ +// constructor(expression, parameterTypeRegistry) { +// // Does not include (){} characters because they have special meaning +// const ESCAPE_REGEXP = /([\\^[$.|?*+])/g +// const PARAMETER_REGEXP = /{([^}]+)}/g +// const OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/g +// const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^/]+)((\/[^\s^/]+)+)/g +// +// this._expression = expression +// this._parameterTypes = [] +// let regexp = '^' +// let match +// let matchOffset = 0 +// +// // Does not include (){} because they have special meaning +// +// expression = expression.replace(ESCAPE_REGEXP, '\\$1') +// +// // Create non-capturing, optional capture groups from parenthesis +// expression = expression.replace( +// OPTIONAL_REGEXP, +// (match, p1, p2) => (p1 === '\\\\' ? `\\(${p2}\\)` : `(?:${p2})?`) +// ) +// +// expression = expression.replace( +// ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, +// (_, p1, p2) => `(?:${p1}${p2.replace(/\//g, '|')})` +// ) +// +// while ((match = PARAMETER_REGEXP.exec(expression)) !== null) { +// const typeName = match[1] +// +// const parameterType = parameterTypeRegistry.lookupByTypeName(typeName) +// if (!parameterType) throw new UndefinedParameterTypeError(typeName) +// this._parameterTypes.push(parameterType) +// +// const text = expression.slice(matchOffset, match.index) +// const captureRegexp = buildCaptureRegexp(parameterType.regexps) +// matchOffset = PARAMETER_REGEXP.lastIndex +// regexp += text +// regexp += captureRegexp +// } +// regexp += expression.slice(matchOffset) +// regexp += '$' +// this._treeRegexp = new TreeRegexp(regexp) +// } +// +// match(text) { +// return Argument.build(this._treeRegexp, text, this._parameterTypes) +// } +// +// get regexp() { +// return this._treeRegexp.regexp +// } +// +// get source() { +// return this._expression +// } +// } +// +// function buildCaptureRegexp(regexps) { +// if (regexps.length === 1) { +// return `(${regexps[0]})` +// } +// +// const captureGroups = regexps.map(group => { +// return `(?:${group})` +// }) +// +// return `(${captureGroups.join('|')})` +// } +// +// module.exports = CucumberExpression diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go new file mode 100644 index 0000000000..b4fd0f0ad4 --- /dev/null +++ b/cucumber-expressions/go/parameter_type.go @@ -0,0 +1,109 @@ +package cucumberexpressions + +type ParameterType struct { + name string + regexps []string + type1 string // Cannot have a field named type as hit a compile error + transform func(...string) interface{} + useForSnippets bool + preferForRegexpMatch bool +} + +func NewParameterType(name string, regexps []string, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) *ParameterType { + if transform == nil { + transform = func(s ...string) interface{} { + return s + } + } + return &ParameterType{ + name: name, + regexps: regexps, + type1: type1, + transform: transform, + useForSnippets: useForSnippets, + preferForRegexpMatch: preferForRegexpMatch, + } +} + +func (p *ParameterType) Name() string { + return p.name +} + +func (p *ParameterType) Regexps() []string { + return p.regexps +} + +func (p *ParameterType) Type() string { + return p.type1 +} + +func (p *ParameterType) UseForSnippets() bool { + return p.useForSnippets +} + +func (p *ParameterType) PreferForRegexpMatch() bool { + return p.preferForRegexpMatch +} + +func (p *ParameterType) Transform(groupValues []string) []interface{} { + return +} + +// +// transform(thisObj, groupValues) { +// let args +// if (this._transform.length === 1) { +// // transform function with arity 1. +// const nonNullGroupValues = groupValues.filter( +// v => v !== null && v !== undefined +// ) +// if (nonNullGroupValues.length >= 2) +// throw new CucumberExpressionError( +// `Single transformer unexpectedly matched 2 values - "${ +// nonNullGroupValues[0] +// }" and "${nonNullGroupValues[1]}"` +// ) +// args = [nonNullGroupValues[0]] +// } else { +// args = groupValues +// } +// +// return this._transform.apply(thisObj, args) +// } +// } +// +// function stringArray(regexps) { +// const array = Array.isArray(regexps) ? regexps : [regexps] +// return array.map(r => (typeof r === 'string' ? r : regexpSource(r))) +// } +// +// function regexpSource(regexp) { +// const flags = regexpFlags(regexp) +// +// for (const flag of ['g', 'i', 'm', 'y']) { +// if (flags.indexOf(flag) !== -1) +// throw new CucumberExpressionError( +// `ParameterType Regexps can't use flag '${flag}'` +// ) +// } +// return regexp.source +// } +// +// // Backport RegExp.flags for Node 4.x +// // https://github.com/nodejs/node/issues/8390 +// // +// // For some strange reason this is not needed for +// // `./mocha dist/test`, but it is needed for +// // `./mocha dist/test/parameter_type_test.js` +// function regexpFlags(regexp) { +// let flags = regexp.flags +// if (flags === undefined) { +// flags = '' +// if (regexp.ignoreCase) flags += 'i' +// if (regexp.global) flags += 'g' +// if (regexp.multiline) flags += 'm' +// } +// return flags +// } +// +// module.exports = ParameterType From 90f7070a5c51044bbfe74e23990cb42c340acdc5 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 7 Mar 2018 00:36:13 -0800 Subject: [PATCH 02/26] update --- cucumber-expressions/go/group.go | 44 ++++++++ cucumber-expressions/go/group_builder.go | 52 ++++++++++ cucumber-expressions/go/iterator.go | 35 +++++++ cucumber-expressions/go/parameter_type.go | 74 ++------------ cucumber-expressions/go/stack.go | 69 +++++++++++++ cucumber-expressions/go/submatch.go | 7 ++ cucumber-expressions/go/tree_regexp.go | 101 +++++++++++++++++++ cucumber-expressions/go/tree_regexp_test.go | 106 ++++++++++++++++++++ 8 files changed, 423 insertions(+), 65 deletions(-) create mode 100644 cucumber-expressions/go/group.go create mode 100644 cucumber-expressions/go/group_builder.go create mode 100644 cucumber-expressions/go/iterator.go create mode 100644 cucumber-expressions/go/stack.go create mode 100644 cucumber-expressions/go/submatch.go create mode 100644 cucumber-expressions/go/tree_regexp.go create mode 100644 cucumber-expressions/go/tree_regexp_test.go diff --git a/cucumber-expressions/go/group.go b/cucumber-expressions/go/group.go new file mode 100644 index 0000000000..f57e2c8ae3 --- /dev/null +++ b/cucumber-expressions/go/group.go @@ -0,0 +1,44 @@ +package cucumberexpressions + +type Group struct { + value string + start int + end int + children []*Group +} + +func NewGroup(value string, start, end int, children []*Group) *Group { + return &Group{ + value: value, + start: start, + end: end, + children: children, + } +} + +func (g *Group) Value() string { + return g.value +} + +func (g *Group) Start() int { + return g.start +} + +func (g *Group) End() int { + return g.end +} + +func (g *Group) Children() []*Group { + return g.children +} + +func (g *Group) Values() []string { + if len(g.children) == 0 { + return []string{g.value} + } + result := make([]string, len(g.children)) + for i, child := range g.children { + result[i] = child.Value() + } + return result +} diff --git a/cucumber-expressions/go/group_builder.go b/cucumber-expressions/go/group_builder.go new file mode 100644 index 0000000000..3ecc92847d --- /dev/null +++ b/cucumber-expressions/go/group_builder.go @@ -0,0 +1,52 @@ +package cucumberexpressions + +type GroupBuilder struct { + groupBuilders []*GroupBuilder + capturing bool + source string +} + +func NewGroupBuilder() *GroupBuilder { + return &GroupBuilder{ + capturing: true, + } +} + +func (g *GroupBuilder) Add(groupBuilder *GroupBuilder) { + g.groupBuilders = append(g.groupBuilders, groupBuilder) +} + +func (g *GroupBuilder) Build(submatches []*Submatch, indexIterator *IntIterator) *Group { + submatch := submatches[indexIterator.Next()] + children := make([]*Group, len(g.groupBuilders)) + for i, child := range g.groupBuilders { + children[i] = child.Build(submatches, indexIterator) + } + return NewGroup(submatch.value, submatch.start, submatch.end, children) +} + +func (g *GroupBuilder) SetNonCapturing() { + g.capturing = false +} + +func (g *GroupBuilder) Capturing() bool { + return g.capturing +} + +func (g *GroupBuilder) Children() []*GroupBuilder { + return g.groupBuilders +} + +func (g *GroupBuilder) MoveChildrenTo(groupBuilder *GroupBuilder) { + for _, child := range g.groupBuilders { + groupBuilder.Add(child) + } +} + +func (g *GroupBuilder) SetSource(value string) { + g.source = value +} + +func (g *GroupBuilder) Source() string { + return g.source +} diff --git a/cucumber-expressions/go/iterator.go b/cucumber-expressions/go/iterator.go new file mode 100644 index 0000000000..71d38535de --- /dev/null +++ b/cucumber-expressions/go/iterator.go @@ -0,0 +1,35 @@ +package cucumberexpressions + +type InterfaceIterator struct { + elements []interface{} + index int +} + +func (i *InterfaceIterator) Next() interface{} { + if i.index >= len(i.elements) { + panic("cannot get next") + } + oldIndex := i.index + i.index++ + return i.elements[oldIndex] +} + +type IntIterator struct { + iterator InterfaceIterator +} + +func NewIntIterator(size int) *IntIterator { + elements := make([]interface{}, size) + for i := 0; i < size; i++ { + elements[i] = i + } + return &IntIterator{ + iterator: InterfaceIterator{ + elements: elements, + }, + } +} + +func (s *IntIterator) Next() int { + return s.iterator.Next().(int) +} diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index b4fd0f0ad4..6119470eec 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -1,20 +1,23 @@ package cucumberexpressions +import "regexp" + type ParameterType struct { name string - regexps []string + regexps []regexp.Regexp type1 string // Cannot have a field named type as hit a compile error transform func(...string) interface{} useForSnippets bool preferForRegexpMatch bool } -func NewParameterType(name string, regexps []string, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) *ParameterType { +func NewParameterType(name string, regexps []regexp.Regexp, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) (*ParameterType, error) { if transform == nil { transform = func(s ...string) interface{} { return s } } + // TODO error if uses flags return &ParameterType{ name: name, regexps: regexps, @@ -22,14 +25,14 @@ func NewParameterType(name string, regexps []string, type1 string, transform fun transform: transform, useForSnippets: useForSnippets, preferForRegexpMatch: preferForRegexpMatch, - } + }, nil } func (p *ParameterType) Name() string { return p.name } -func (p *ParameterType) Regexps() []string { +func (p *ParameterType) Regexps() []regexp.Regexp { return p.regexps } @@ -45,65 +48,6 @@ func (p *ParameterType) PreferForRegexpMatch() bool { return p.preferForRegexpMatch } -func (p *ParameterType) Transform(groupValues []string) []interface{} { - return +func (p *ParameterType) Transform(groupValues []string) interface{} { + return p.transform(groupValues...) } - -// -// transform(thisObj, groupValues) { -// let args -// if (this._transform.length === 1) { -// // transform function with arity 1. -// const nonNullGroupValues = groupValues.filter( -// v => v !== null && v !== undefined -// ) -// if (nonNullGroupValues.length >= 2) -// throw new CucumberExpressionError( -// `Single transformer unexpectedly matched 2 values - "${ -// nonNullGroupValues[0] -// }" and "${nonNullGroupValues[1]}"` -// ) -// args = [nonNullGroupValues[0]] -// } else { -// args = groupValues -// } -// -// return this._transform.apply(thisObj, args) -// } -// } -// -// function stringArray(regexps) { -// const array = Array.isArray(regexps) ? regexps : [regexps] -// return array.map(r => (typeof r === 'string' ? r : regexpSource(r))) -// } -// -// function regexpSource(regexp) { -// const flags = regexpFlags(regexp) -// -// for (const flag of ['g', 'i', 'm', 'y']) { -// if (flags.indexOf(flag) !== -1) -// throw new CucumberExpressionError( -// `ParameterType Regexps can't use flag '${flag}'` -// ) -// } -// return regexp.source -// } -// -// // Backport RegExp.flags for Node 4.x -// // https://github.com/nodejs/node/issues/8390 -// // -// // For some strange reason this is not needed for -// // `./mocha dist/test`, but it is needed for -// // `./mocha dist/test/parameter_type_test.js` -// function regexpFlags(regexp) { -// let flags = regexp.flags -// if (flags === undefined) { -// flags = '' -// if (regexp.ignoreCase) flags += 'i' -// if (regexp.global) flags += 'g' -// if (regexp.multiline) flags += 'm' -// } -// return flags -// } -// -// module.exports = ParameterType diff --git a/cucumber-expressions/go/stack.go b/cucumber-expressions/go/stack.go new file mode 100644 index 0000000000..ddf5a6aef9 --- /dev/null +++ b/cucumber-expressions/go/stack.go @@ -0,0 +1,69 @@ +package cucumberexpressions + +type InterfaceStack struct { + elements []interface{} +} + +func (i *InterfaceStack) Len() int { + return len(i.elements) +} + +func (i *InterfaceStack) Peek() interface{} { + if i.Len() == 0 { + panic("cannot peek") + } + return i.elements[i.Len()-1] +} + +func (i *InterfaceStack) Pop() interface{} { + if i.Len() == 0 { + panic("cannot pop") + } + value := i.elements[i.Len()-1] + i.elements = i.elements[:i.Len()-1] + return value +} + +func (i *InterfaceStack) Push(value interface{}) { + i.elements = append(i.elements, value) +} + +type GroupBuilderStack struct { + interfaceStack InterfaceStack +} + +func (s *GroupBuilderStack) Len() int { + return s.interfaceStack.Len() +} + +func (s *GroupBuilderStack) Peek() *GroupBuilder { + return s.interfaceStack.Peek().(*GroupBuilder) +} + +func (s *GroupBuilderStack) Pop() *GroupBuilder { + return s.interfaceStack.Pop().(*GroupBuilder) +} + +func (s *GroupBuilderStack) Push(value *GroupBuilder) { + s.interfaceStack.Push(value) +} + +type IntStack struct { + interfaceStack InterfaceStack +} + +func (s *IntStack) Len() int { + return s.interfaceStack.Len() +} + +func (s *IntStack) Peek() int { + return s.interfaceStack.Peek().(int) +} + +func (s *IntStack) Pop() int { + return s.interfaceStack.Pop().(int) +} + +func (s *IntStack) Push(value int) { + s.interfaceStack.Push(value) +} diff --git a/cucumber-expressions/go/submatch.go b/cucumber-expressions/go/submatch.go new file mode 100644 index 0000000000..8e71770cc3 --- /dev/null +++ b/cucumber-expressions/go/submatch.go @@ -0,0 +1,7 @@ +package cucumberexpressions + +type Submatch struct { + value string + start int + end int +} diff --git a/cucumber-expressions/go/tree_regexp.go b/cucumber-expressions/go/tree_regexp.go new file mode 100644 index 0000000000..ec1dc13408 --- /dev/null +++ b/cucumber-expressions/go/tree_regexp.go @@ -0,0 +1,101 @@ +package cucumberexpressions + +import ( + "regexp" +) + +type TreeRegexp struct { + regexp *regexp.Regexp + groupBuilder *GroupBuilder +} + +func NewTreeRegexp(regexp *regexp.Regexp) *TreeRegexp { + stack := GroupBuilderStack{} + stack.Push(NewGroupBuilder()) + groupStartStack := IntStack{} + var last rune + escaping := false + nonCapturingMaybe := false + for n, c := range regexp.String() { + if c == '(' && !escaping { + stack.Push(NewGroupBuilder()) + groupStartStack.Push(n + 1) + nonCapturingMaybe = false + } else if c == ')' && !escaping { + gb := stack.Pop() + groupStart := groupStartStack.Pop() + if gb.Capturing() { + gb.SetSource(regexp.String()[groupStart:n]) + stack.Peek().Add(gb) + } else { + gb.MoveChildrenTo(stack.Peek()) + } + nonCapturingMaybe = false + } else if c == '?' && last == '(' { + nonCapturingMaybe = true + } else if nonCapturingMaybe { + if c == ':' || isFlagCharacter(c) { + stack.Peek().SetNonCapturing() + } + nonCapturingMaybe = false + } + escaping = c == '\\' && !escaping + last = c + } + + return &TreeRegexp{ + regexp: regexp, + groupBuilder: stack.Pop(), + } +} + +func (t *TreeRegexp) Regexp() *regexp.Regexp { + return t.regexp +} + +func (t *TreeRegexp) GroupBuilder() *GroupBuilder { + return t.groupBuilder +} + +func (t *TreeRegexp) Match(s string) *Group { + indicies := t.Regexp().FindAllStringSubmatchIndex(s, -1) + if indicies == nil { + return nil + } + var submatches []*Submatch + for i := range indicies[0] { + if i%2 == 0 { + continue + } + start, end := indicies[0][i-1], indicies[0][i] + value := "" + if start != -1 { + value = s[start:end] + } + submatches = append(submatches, &Submatch{ + value: value, + start: start, + end: end, + }) + } + return t.groupBuilder.Build(submatches, NewIntIterator(len(submatches))) +} + +func isFlagCharacter(c rune) bool { + switch c { + case 'i', 'm', 's', 'U', '-': + return true + default: + return false + } +} + +// +// match(s) { +// const match = this._regex.exec(s) +// if (!match) return null +// let groupIndex = 0 +// const nextGroupIndex = () => groupIndex++ +// return this._groupBuilder.build(match, nextGroupIndex) +// } +// } diff --git a/cucumber-expressions/go/tree_regexp_test.go b/cucumber-expressions/go/tree_regexp_test.go new file mode 100644 index 0000000000..be7f71d4fc --- /dev/null +++ b/cucumber-expressions/go/tree_regexp_test.go @@ -0,0 +1,106 @@ +package cucumberexpressions_test + +import ( + "regexp" + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestTreeRegexp(t *testing.T) { + t.Run("exposes group source", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile("(a(?:b)?)(c)")) + var gbSources []string + for _, gb := range tr.GroupBuilder().Children() { + gbSources = append(gbSources, gb.Source()) + } + require.Equal(t, gbSources, []string{"a(?:b)?", "c"}) + }) + + t.Run("builds tree, ignoring non-capturing groups", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile("(a(?:b)?)(c)")) + group := tr.Match("ac") + require.Equal(t, group.Value(), "ac") + require.Equal(t, group.Children()[0].Value(), "a") + require.Empty(t, group.Children()[0].Children()) + require.Equal(t, group.Children()[1].Value(), "c") + }) + + t.Run("matches optional group", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile("^Something( with an optional argument)?")) + group := tr.Match("Something") + require.Equal(t, group.Children()[0].Value(), "") + }) + + t.Run("matches nested groups", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+))`)) + group := tr.Match("A 5 thick line from 10,20,30 to 40,50,60") + require.Equal(t, group.Children()[0].Value(), "5") + require.Equal(t, group.Children()[1].Value(), "10,20,30") + require.Equal(t, group.Children()[1].Children()[0].Value(), "10") + require.Equal(t, group.Children()[1].Children()[1].Value(), "20") + require.Equal(t, group.Children()[1].Children()[2].Value(), "30") + require.Equal(t, group.Children()[2].Value(), "40,50,60") + require.Equal(t, group.Children()[2].Children()[0].Value(), "40") + require.Equal(t, group.Children()[2].Children()[1].Value(), "50") + require.Equal(t, group.Children()[2].Children()[2].Value(), "60") + }) + + t.Run("detects multiple non capturing groups", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`(?:a)(:b)(\?c)(d)`)) + group := tr.Match("a:b?cd") + require.Len(t, group.Children(), 3) + }) + + t.Run("works with escaped backslash", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`foo\\(bar|baz)`)) + group := tr.Match("foo\\bar") + require.Len(t, group.Children(), 1) + }) + + t.Run("works with escaped slash", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`I go to '\/(.+)'$`)) + group := tr.Match("I go to '/hello'") + require.Len(t, group.Children(), 1) + }) + + t.Run("works with digit and word", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`^(\d) (\w+)$`)) + group := tr.Match("2 you") + require.Len(t, group.Children(), 2) + }) + + t.Run("captures non capturing groups with capturing groups inside", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile(`the stdout(?: from "(.*?)")?`)) + group := tr.Match("the stdout") + require.Equal(t, group.Value(), "the stdout") + require.Equal(t, group.Children()[0].Value(), "") + require.Len(t, group.Children(), 1) + }) + + t.Run("works with flags", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile("(?i)HELLO")) + var gbSources []string + for _, gb := range tr.GroupBuilder().Children() { + gbSources = append(gbSources, gb.Source()) + } + require.Empty(t, gbSources) + group := tr.Match("hello") + require.Equal(t, group.Value(), "hello") + }) + + t.Run("works with disabled flags", func(t *testing.T) { + tr := cucumberexpressions.NewTreeRegexp(regexp.MustCompile("(?i)HELL(?-i:O)")) + var gbSources []string + for _, gb := range tr.GroupBuilder().Children() { + gbSources = append(gbSources, gb.Source()) + } + require.Empty(t, gbSources) + group := tr.Match("hello") + require.Nil(t, group) + group = tr.Match("hellO") + require.Equal(t, group.Value(), "hellO") + }) + +} From 333bba5d317d2da31aed83a801d076a197dce74d Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 7 Mar 2018 11:55:38 -0800 Subject: [PATCH 03/26] update --- .../go/generated_expression.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 cucumber-expressions/go/generated_expression.go diff --git a/cucumber-expressions/go/generated_expression.go b/cucumber-expressions/go/generated_expression.go new file mode 100644 index 0000000000..e04573b45b --- /dev/null +++ b/cucumber-expressions/go/generated_expression.go @@ -0,0 +1,48 @@ +package cucumberexpressions + +import "fmt" + +type GeneratedExpression struct { + expressionTemplate string + parameterTypes []*ParameterType +} + +func NewGeneratedExpression(expressionTemplate string, parameterTypes []*ParameterType) *GeneratedExpression { + return &GeneratedExpression{ + expressionTemplate: expressionTemplate, + parameterTypes: parameterTypes, + } +} + +func (g *GeneratedExpression) Source() string { + names := make([]string, len(g.parameterTypes)) + for i, p := range g.parameterTypes { + names[i] = p.Name() + } + return fmt.Sprintf(g.expressionTemplate, names) +} + +func (g *GeneratedExpression) ParameterNames() []string { + usageByTypeName := map[string]int{} + result := make([]string, len(g.parameterTypes)) + for i, p := range g.parameterTypes { + result[i] = getParameterName(p.Name(), usageByTypeName) + } + return result +} + +func (g *GeneratedExpression) ParameterTypes() []*ParameterType { + return g.parameterTypes +} + +func getParameterName(typeName string, usageByTypeName map[string]int) string { + count, ok := usageByTypeName[typeName] + if !ok { + count = 0 + } + usageByTypeName[typeName] = count + 1 + if count == 1 { + return typeName + } + return fmt.Sprintf("%s%d", typeName, count) +} From b2b53fa32cb383dda773d1a5fa3d2cf980436d9d Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 7 Mar 2018 11:57:32 -0800 Subject: [PATCH 04/26] update --- cucumber-expressions/go/tree_regexp.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cucumber-expressions/go/tree_regexp.go b/cucumber-expressions/go/tree_regexp.go index ec1dc13408..cae3198d5c 100644 --- a/cucumber-expressions/go/tree_regexp.go +++ b/cucumber-expressions/go/tree_regexp.go @@ -89,13 +89,3 @@ func isFlagCharacter(c rune) bool { return false } } - -// -// match(s) { -// const match = this._regex.exec(s) -// if (!match) return null -// let groupIndex = 0 -// const nextGroupIndex = () => groupIndex++ -// return this._groupBuilder.build(match, nextGroupIndex) -// } -// } From f55a53c370136e0ed051d9b24d193da43bfa66d8 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 7 Mar 2018 12:46:55 -0800 Subject: [PATCH 05/26] update --- ...binatorial_generated_expression_factory.go | 48 ++++++++++++ ...orial_generated_expression_factory_test.go | 76 +++++++++++++++++++ .../go/generated_expression.go | 4 +- cucumber-expressions/go/parameter_type.go | 6 +- 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 cucumber-expressions/go/combinatorial_generated_expression_factory.go create mode 100644 cucumber-expressions/go/combinatorial_generated_expression_factory_test.go diff --git a/cucumber-expressions/go/combinatorial_generated_expression_factory.go b/cucumber-expressions/go/combinatorial_generated_expression_factory.go new file mode 100644 index 0000000000..4e74063982 --- /dev/null +++ b/cucumber-expressions/go/combinatorial_generated_expression_factory.go @@ -0,0 +1,48 @@ +package cucumberexpressions + +type CombinatorialGeneratedExpressionFactory struct { + expressionTemplate string + parameterTypeCombinations [][]*ParameterType +} + +func NewCombinatorialGeneratedExpressionFactory(expressionTemplate string, parameterTypeCombinations [][]*ParameterType) *CombinatorialGeneratedExpressionFactory { + return &CombinatorialGeneratedExpressionFactory{ + expressionTemplate: expressionTemplate, + parameterTypeCombinations: parameterTypeCombinations, + } +} + +func (c *CombinatorialGeneratedExpressionFactory) GenerateExpressions() []*GeneratedExpression { + generatedExpressions := &GeneratedExpressionList{} + c.generatePermutations(generatedExpressions, 0, nil) + return generatedExpressions.ToArray() +} + +func (c *CombinatorialGeneratedExpressionFactory) generatePermutations(generatedExpressions *GeneratedExpressionList, depth int, currentParameterTypes []*ParameterType) { + if depth == len(c.parameterTypeCombinations) { + generatedExpressions.Push( + NewGeneratedExpression(c.expressionTemplate, currentParameterTypes), + ) + return + } + + for _, parameterType := range c.parameterTypeCombinations[depth] { + c.generatePermutations( + generatedExpressions, + depth+1, + append(currentParameterTypes, parameterType), + ) + } +} + +type GeneratedExpressionList struct { + elements []*GeneratedExpression +} + +func (g *GeneratedExpressionList) Push(expr *GeneratedExpression) { + g.elements = append(g.elements, expr) +} + +func (g *GeneratedExpressionList) ToArray() []*GeneratedExpression { + return g.elements +} diff --git a/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go b/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go new file mode 100644 index 0000000000..840d437a44 --- /dev/null +++ b/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go @@ -0,0 +1,76 @@ +package cucumberexpressions_test + +import ( + "regexp" + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestCombinatorialGeneratedExpressionFactory(t *testing.T) { + t.Run("generates multiple expressions", func(t *testing.T) { + colorParameterType, err := cucumberexpressions.NewParameterType( + "color", + []*regexp.Regexp{regexp.MustCompile("red|blue|yellow")}, + "", + func(arg3 ...string) interface{} { return arg3[0] }, + false, + true, + ) + require.NoError(t, err) + csscolorParameterType, err := cucumberexpressions.NewParameterType( + "csscolor", + []*regexp.Regexp{regexp.MustCompile("red|blue|yellow")}, + "", + func(arg3 ...string) interface{} { return arg3[0] }, + false, + true, + ) + require.NoError(t, err) + dateParameterType, err := cucumberexpressions.NewParameterType( + "date", + []*regexp.Regexp{regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)}, + "", + func(arg3 ...string) interface{} { return arg3[0] }, + false, + true, + ) + require.NoError(t, err) + datetimeParameterType, err := cucumberexpressions.NewParameterType( + "datetime", + []*regexp.Regexp{regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)}, + "", + func(arg3 ...string) interface{} { return arg3[0] }, + false, + true, + ) + require.NoError(t, err) + timestampParameterType, err := cucumberexpressions.NewParameterType( + "timestamp", + []*regexp.Regexp{regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)}, + "", + func(arg3 ...string) interface{} { return arg3[0] }, + false, + true, + ) + require.NoError(t, err) + parameterTypeCombinations := [][]*cucumberexpressions.ParameterType{ + {colorParameterType, csscolorParameterType}, + {dateParameterType, datetimeParameterType, timestampParameterType}, + } + factory := cucumberexpressions.NewCombinatorialGeneratedExpressionFactory("I bought a {%s} ball on {%s}", parameterTypeCombinations) + var expressions []string + for _, g := range factory.GenerateExpressions() { + expressions = append(expressions, g.Source()) + } + require.Equal(t, expressions, []string{ + "I bought a {color} ball on {date}", + "I bought a {color} ball on {datetime}", + "I bought a {color} ball on {timestamp}", + "I bought a {csscolor} ball on {date}", + "I bought a {csscolor} ball on {datetime}", + "I bought a {csscolor} ball on {timestamp}", + }) + }) +} diff --git a/cucumber-expressions/go/generated_expression.go b/cucumber-expressions/go/generated_expression.go index e04573b45b..ceacc8cefd 100644 --- a/cucumber-expressions/go/generated_expression.go +++ b/cucumber-expressions/go/generated_expression.go @@ -15,11 +15,11 @@ func NewGeneratedExpression(expressionTemplate string, parameterTypes []*Paramet } func (g *GeneratedExpression) Source() string { - names := make([]string, len(g.parameterTypes)) + names := make([]interface{}, len(g.parameterTypes)) for i, p := range g.parameterTypes { names[i] = p.Name() } - return fmt.Sprintf(g.expressionTemplate, names) + return fmt.Sprintf(g.expressionTemplate, names...) } func (g *GeneratedExpression) ParameterNames() []string { diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index 6119470eec..9e96bb3e78 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -4,14 +4,14 @@ import "regexp" type ParameterType struct { name string - regexps []regexp.Regexp + regexps []*regexp.Regexp type1 string // Cannot have a field named type as hit a compile error transform func(...string) interface{} useForSnippets bool preferForRegexpMatch bool } -func NewParameterType(name string, regexps []regexp.Regexp, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) (*ParameterType, error) { +func NewParameterType(name string, regexps []*regexp.Regexp, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) (*ParameterType, error) { if transform == nil { transform = func(s ...string) interface{} { return s @@ -32,7 +32,7 @@ func (p *ParameterType) Name() string { return p.name } -func (p *ParameterType) Regexps() []regexp.Regexp { +func (p *ParameterType) Regexps() []*regexp.Regexp { return p.regexps } From 4d097cddfca577099588c73c9712c6365fff7d3b Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 7 Mar 2018 15:05:56 -0800 Subject: [PATCH 06/26] update --- Untitled | 0 cucumber-expressions/go/argument.go | 82 ++++++++++++----------------- 2 files changed, 34 insertions(+), 48 deletions(-) create mode 100644 Untitled diff --git a/Untitled b/Untitled new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cucumber-expressions/go/argument.go b/cucumber-expressions/go/argument.go index edc9545184..2601b2f142 100644 --- a/cucumber-expressions/go/argument.go +++ b/cucumber-expressions/go/argument.go @@ -1,53 +1,39 @@ package cucumberexpressions +import "fmt" + type Argument struct { - group string - parameterType string + group *Group + parameterType *ParameterType +} + +func BuildArguments(treeRegexp *TreeRegexp, text string, parameterTypes []*ParameterType) []*Argument { + group := treeRegexp.Match(text) + if group == nil { + return nil + } + argGroups := group.Children() + if len(argGroups) != len(parameterTypes) { + panic(fmt.Errorf("%s has %d capture groups (%v), but there were %d parameter types (%v)", treeRegexp.Regexp().String(), len(argGroups), argGroups, len(parameterTypes), parameterTypes)) + } + arguments := make([]*Argument, len(parameterTypes)) + for i, parameterType := range parameterTypes { + arguments[i] = NewArgument(argGroups[i], parameterType) + } + return arguments +} + +func NewArgument(group *Group, parameterType *ParameterType) *Argument { + return &Argument{ + group: group, + parameterType: parameterType, + } +} + +func (a *Argument) Group() *Group { + return a.group } -// -// const { CucumberExpressionError } = require('./errors') -// -// class Argument { -// static build(treeRegexp, text, parameterTypes) { -// const group = treeRegexp.match(text) -// if (!group) return null -// -// const argGroups = group.children -// -// if (argGroups.length !== parameterTypes.length) { -// throw new CucumberExpressionError( -// `Expression ${treeRegexp.regexp} has ${ -// argGroups.length -// } capture groups (${argGroups.map(g => g.value)}), but there were ${ -// parameterTypes.length -// } parameter types (${parameterTypes.map(p => p.name)})` -// ) -// } -// -// return parameterTypes.map( -// (parameterType, i) => new Argument(argGroups[i], parameterType) -// ) -// } -// -// constructor(group, parameterType) { -// this._group = group -// this._parameterType = parameterType -// } -// -// get group() { -// return this._group -// } -// -// /** -// * Get the value returned by the parameter type's transformer function. -// * -// * @param thisObj the object in which the transformer function is applied. -// */ -// getValue(thisObj) { -// let groupValues = this._group ? this._group.values : null -// return this._parameterType.transform(thisObj, groupValues) -// } -// } -// -// module.exports = Argument +func (a *Argument) GetValue() interface{} { + return a.parameterType.Transform(a.group.Values()) +} From bf1d10b72ad0ee9b6d12ee7f9fc2a803be41e16f Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 21 Mar 2018 16:39:03 -0700 Subject: [PATCH 07/26] update --- .../go/cucumber_expression_generator.go | 118 +++++++++++++ .../go/parameter_type_matcher.go | 68 ++++++++ .../go/parameter_type_registry.go | 163 ++++++++++++++++++ cucumber-expressions/go/regular_expression.go | 1 + .../go/regular_expression_test.go | 1 + 5 files changed, 351 insertions(+) create mode 100644 cucumber-expressions/go/cucumber_expression_generator.go create mode 100644 cucumber-expressions/go/parameter_type_matcher.go create mode 100644 cucumber-expressions/go/parameter_type_registry.go create mode 100644 cucumber-expressions/go/regular_expression.go create mode 100644 cucumber-expressions/go/regular_expression_test.go diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go new file mode 100644 index 0000000000..ac1820ddad --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -0,0 +1,118 @@ +package cucumberexpressions + +// const util = require('util') +// const ParameterTypeMatcher = require('./parameter_type_matcher') +// const ParameterType = require('./parameter_type') +// const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory') +// +// class CucumberExpressionGenerator { +// constructor(parameterTypeRegistry) { +// this._parameterTypeRegistry = parameterTypeRegistry +// } +// +// generateExpressions(text) { +// const parameterTypeCombinations = [] +// const parameterTypeMatchers = this._createParameterTypeMatchers(text) +// let expressionTemplate = '' +// let pos = 0 +// +// // eslint-disable-next-line no-constant-condition +// while (true) { +// let matchingParameterTypeMatchers = [] +// +// for (const parameterTypeMatcher of parameterTypeMatchers) { +// const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos) +// if (advancedParameterTypeMatcher.find) { +// matchingParameterTypeMatchers.push(advancedParameterTypeMatcher) +// } +// } +// +// if (matchingParameterTypeMatchers.length > 0) { +// matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort( +// ParameterTypeMatcher.compare +// ) +// +// // Find all the best parameter type matchers, they are all candidates. +// const bestParameterTypeMatcher = matchingParameterTypeMatchers[0] +// const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter( +// m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0 +// ) +// +// // Build a list of parameter types without duplicates. The reason there +// // might be duplicates is that some parameter types have more than one regexp, +// // which means multiple ParameterTypeMatcher objects will have a reference to the +// // same ParameterType. +// // We're sorting the list so preferential parameter types are listed first. +// // Users are most likely to want these, so they should be listed at the top. +// let parameterTypes = [] +// for (const parameterTypeMatcher of bestParameterTypeMatchers) { +// if ( +// parameterTypes.indexOf(parameterTypeMatcher.parameterType) === -1 +// ) { +// parameterTypes.push(parameterTypeMatcher.parameterType) +// } +// } +// parameterTypes = parameterTypes.sort(ParameterType.compare) +// +// parameterTypeCombinations.push(parameterTypes) +// +// expressionTemplate += escapeForUtilFormat( +// text.slice(pos, bestParameterTypeMatcher.start) +// ) +// expressionTemplate += '{%s}' +// +// pos = +// bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length +// } else { +// break +// } +// +// if (pos >= text.length) { +// break +// } +// } +// +// expressionTemplate += escapeForUtilFormat(text.slice(pos)) +// return new CombinatorialGeneratedExpressionFactory( +// expressionTemplate, +// parameterTypeCombinations +// ).generateExpressions() +// } +// +// /** +// * @deprecated +// */ +// generateExpression(text) { +// return util.deprecate( +// () => this.generateExpressions(text)[0], +// 'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead' +// )() +// } +// +// _createParameterTypeMatchers(text) { +// let parameterMatchers = [] +// for (const parameterType of this._parameterTypeRegistry.parameterTypes) { +// if (parameterType.useForSnippets) { +// parameterMatchers = parameterMatchers.concat( +// this._createParameterTypeMatchers2(parameterType, text) +// ) +// } +// } +// return parameterMatchers +// } +// +// _createParameterTypeMatchers2(parameterType, text) { +// // TODO: [].map +// const result = [] +// for (const regexp of parameterType.regexps) { +// result.push(new ParameterTypeMatcher(parameterType, regexp, text)) +// } +// return result +// } +// } +// +// function escapeForUtilFormat(s) { +// return s.replace(/%/g, '%%') +// } +// +// module.exports = CucumberExpressionGenerator diff --git a/cucumber-expressions/go/parameter_type_matcher.go b/cucumber-expressions/go/parameter_type_matcher.go new file mode 100644 index 0000000000..1ebcf7faee --- /dev/null +++ b/cucumber-expressions/go/parameter_type_matcher.go @@ -0,0 +1,68 @@ +package cucumberexpressions + +import ( + "fmt" + "regexp" +) + +type ParameterTypeMatcher struct { + parameterType *ParameterType + regexp *regexp.Regexp + text string + matchPosition int + match []int +} + +func NewParameterTypeMatcher(parameterType *ParameterType, r *regexp.Regexp, text string, matchPosition int) *ParameterTypeMatcher { + captureGroupRegexp := regexp.MustCompile(fmt.Sprintf("(%s)", r.String())) + return &ParameterTypeMatcher{ + parameterType: parameterType, + regexp: r, + text: text, + matchPosition: matchPosition, + match: captureGroupRegexp.FindStringIndex(text[matchPosition:]), + } +} + +func (p *ParameterTypeMatcher) ParameterType() *ParameterType { + return p.parameterType +} + +func (p *ParameterTypeMatcher) AdvanceTo(newMatchPosition int) *ParameterTypeMatcher { + return NewParameterTypeMatcher(p.parameterType, p.regexp, p.text, newMatchPosition) +} + +func (p *ParameterTypeMatcher) Find() bool { + return p.match != nil && p.Group() != "" +} + +func (p *ParameterTypeMatcher) Start() int { + return p.matchPosition + p.match[0] +} + +func (p *ParameterTypeMatcher) Group() string { + return p.text[p.matchPosition:][p.match[0]:p.match[1]] +} + +type ParameterTypeSlice []*ParameterType + +func (p ParameterTypeSlice) Len() int { + return len(p) +} +func (p ParameterTypeSlice) Less(i, j int) bool { + posComparison := p[i].UseForSnippets() + return p[i] < p[j] +} +func (p ParameterTypeSlice) Swap(i, j int) { + p[i], p[j] = p[j], p[i] + } + +func []ParameterType(a, b) + +// static compare(a, b) { +// const posComparison = a.start - b.start +// if (posComparison !== 0) return posComparison +// const lengthComparison = b.group.length - a.group.length +// if (lengthComparison !== 0) return lengthComparison +// return 0 +// } diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go new file mode 100644 index 0000000000..3bca04b2e0 --- /dev/null +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -0,0 +1,163 @@ +package cucumberexpressions + +import ( + "fmt" + "regexp" + "sort" + "strconv" +) + +var INTEGER_REGEXPS = []*regexp.Regexp{ + regexp.MustCompile(`-?\d+`), + regexp.MustCompile(`\d+`), +} +var FLOAT_REGEXPS = []*regexp.Regexp{ + regexp.MustCompile(`-?\d*\.\d+`), +} +var WORD_REGEXPS = []*regexp.Regexp{ + regexp.MustCompile(`\w+`), +} +var STRING_REGEXPS = []*regexp.Regexp{ + regexp.MustCompile(`"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'`), +} + +type ParameterTypeRegistry struct { + parameterTypeByName map[string]*ParameterType + parameterTypesByRegexp map[string][]*ParameterType +} + +func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { + result := &ParameterTypeRegistry{ + parameterTypeByName: map[string]*ParameterType{}, + parameterTypesByRegexp: map[string][]*ParameterType{}, + } + intParameterType, err := NewParameterType( + "int", + INTEGER_REGEXPS, + "int", + func(arg3 ...string) interface{} { + i, err := strconv.Atoi(arg3[0]) + if err != nil { + panic(err) + } + return i + }, + true, + true, + ) + if err != nil { + return nil, err + } + result.DefineParameterType(intParameterType) + floatParameterType, err := NewParameterType( + "float", + FLOAT_REGEXPS, + "float", + func(arg3 ...string) interface{} { + f, err := strconv.ParseFloat(arg3[0], 64) + if err != nil { + panic(err) + } + return f + }, + true, + false, + ) + if err != nil { + return nil, err + } + result.DefineParameterType(floatParameterType) + wordParameterType, err := NewParameterType( + "word", + WORD_REGEXPS, + "string", + func(arg3 ...string) interface{} { + return arg3[0] + }, + false, + false, + ) + if err != nil { + return nil, err + } + result.DefineParameterType(wordParameterType) + stringParameterType, err := NewParameterType( + "string", + STRING_REGEXPS, + "string", + func(arg3 ...string) interface{} { + return arg3[0] + }, + true, + false, + ) + if err != nil { + return nil, err + } + result.DefineParameterType(stringParameterType) + return result, nil +} + +func (p *ParameterTypeRegistry) GetParamaterTypes() []*ParameterType { + result := make([]*ParameterType, len(p.parameterTypeByName)) + index := 0 + for _, parameterType := range p.parameterTypeByName { + result[index] = parameterType + index++ + } + return result +} + +func (p *ParameterTypeRegistry) LookupByTypeName(name string) *ParameterType { + return p.parameterTypeByName[name] +} + +func (p *ParameterTypeRegistry) LookupByRegexp(parameterTypeRegexp string) (*ParameterType, error) { + parameterTypes, ok := p.parameterTypesByRegexp[parameterTypeRegexp] + if !ok { + return nil, nil + } + if len(parameterTypes) > 1 && !parameterTypes[0].PreferForRegexpMatch() { + // We don't do this check on insertion because we only want to restrict + // ambiguiuty when we look up by Regexp. Users of CucumberExpression should + // not be restricted. + // const generatedExpressions = new CucumberExpressionGenerator( + // this + // ).generateExpressions(text) + // throw new AmbiguousParameterTypeError.forRegExp( + // parameterTypeRegexp, + // expressionRegexp, + // parameterTypes, + // generatedExpressions + // ) + } + return parameterTypes[0], nil +} + +func (p *ParameterTypeRegistry) DefineParameterType(parameterType *ParameterType) error { + if _, ok := p.parameterTypeByName[parameterType.Name()]; ok { + return fmt.Errorf("There is already a parameter type with name %s", parameterType.Name()) + } + p.parameterTypeByName[parameterType.Name()] = parameterType + for _, parameterTypeRegexp := range parameterType.Regexps() { + if _, ok := p.parameterTypesByRegexp[parameterTypeRegexp.String()]; !ok { + p.parameterTypesByRegexp[parameterTypeRegexp.String()] = []*ParameterType{} + } + parameterTypes := p.parameterTypesByRegexp[parameterTypeRegexp.String()] + if len(parameterTypes) > 0 && parameterTypes[0].PreferForRegexpMatch() && parameterType.PreferForRegexpMatch() { + return fmt.Errorf("There can only be one preferential parameter type per regexp. The regexp /%s/ is used for two preferential parameter types, {%s} and {%s}", parameterTypeRegexp.String(), parameterTypes[0].Name(), parameterType.Name()) + } + parameterTypes = append(parameterTypes, parameterType) + sort.Slice(parameterTypes, func(i int, j int) bool { + if parameterTypes[i].PreferForRegexpMatch() && !parameterTypes[j].PreferForRegexpMatch() { + return true + } + if parameterTypes[j].PreferForRegexpMatch() && !parameterTypes[i].PreferForRegexpMatch() { + return false + } + return parameterTypes[i].Name() < parameterTypes[j].Name() + }) + p.parameterTypesByRegexp[parameterTypeRegexp.String()] = parameterTypes + } + return nil +} diff --git a/cucumber-expressions/go/regular_expression.go b/cucumber-expressions/go/regular_expression.go new file mode 100644 index 0000000000..1df51fd0fa --- /dev/null +++ b/cucumber-expressions/go/regular_expression.go @@ -0,0 +1 @@ +package cucumberexpressions diff --git a/cucumber-expressions/go/regular_expression_test.go b/cucumber-expressions/go/regular_expression_test.go new file mode 100644 index 0000000000..fe2be8e5fc --- /dev/null +++ b/cucumber-expressions/go/regular_expression_test.go @@ -0,0 +1 @@ +package cucumberexpressions_test From 0df90654ba1d24042fb6e5d2ded4054e9f7f4383 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 21 Mar 2018 18:36:30 -0700 Subject: [PATCH 08/26] update --- cucumber-expressions/go/argument.go | 2 +- cucumber-expressions/go/errors.go | 92 +++++++++++++++++++ .../go/parameter_type_matcher.go | 15 --- cucumber-expressions/go/regular_expression.go | 57 ++++++++++++ .../go/regular_expression_test.go | 90 ++++++++++++++++++ 5 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 cucumber-expressions/go/errors.go diff --git a/cucumber-expressions/go/argument.go b/cucumber-expressions/go/argument.go index 2601b2f142..efff01d184 100644 --- a/cucumber-expressions/go/argument.go +++ b/cucumber-expressions/go/argument.go @@ -10,7 +10,7 @@ type Argument struct { func BuildArguments(treeRegexp *TreeRegexp, text string, parameterTypes []*ParameterType) []*Argument { group := treeRegexp.Match(text) if group == nil { - return nil + return []*Argument{} } argGroups := group.Children() if len(argGroups) != len(parameterTypes) { diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go new file mode 100644 index 0000000000..b5edd7c8ba --- /dev/null +++ b/cucumber-expressions/go/errors.go @@ -0,0 +1,92 @@ +package cucumberexpressions + +import "fmt" + +func NewCucumberExpressionError(text string) error { + return &CucumberExpressionError{s: text} +} + +type CucumberExpressionError struct { + s string +} + +func (e *CucumberExpressionError) Error() string { + return e.s +} + +func NewAmbiguousParameterTypeErrorForConstructor(keyName, keyValue string, parameterTypes []*ParameterType, generatedExpressions []string) error { + return &AmbiguousParameterTypeError{ + s: fmt.Sprintf( + "parameter type with %s=%s is used by several parameter types: %v, %v", + keyName, + keyValue, + parameterTypes, + generatedExpressions, + ), + } +} + +type AmbiguousParameterTypeError struct { + s string +} + +func (e *AmbiguousParameterTypeError) Error() string { + return e.s +} + +// +// class CucumberExpressionError extends Error {} +// +// class AmbiguousParameterTypeError extends CucumberExpressionError { +// static forConstructor( +// keyName, +// keyValue, +// parameterTypes, +// generatedExpressions +// ) { +// return new this( +// `parameter type with ${keyName}=${keyValue} is used by several parameter types: ${parameterTypes}, ${generatedExpressions}` +// ) +// } +// +// static forRegExp( +// parameterTypeRegexp, +// expressionRegexp, +// parameterTypes, +// generatedExpressions +// ) { +// return new this( +// `Your Regular Expression ${expressionRegexp} +// matches multiple parameter types with regexp ${parameterTypeRegexp}: +// ${this._parameterTypeNames(parameterTypes)} +// +// I couldn't decide which one to use. You have two options: +// +// 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these: +// ${this._expressions(generatedExpressions)} +// +// 2) Make one of the parameter types preferential and continue to use a Regular Expression. +// ` +// ) +// } +// +// static _parameterTypeNames(parameterTypes) { +// return parameterTypes.map(p => `{${p.name}}`).join('\n ') +// } +// +// static _expressions(generatedExpressions) { +// return generatedExpressions.map(e => e.source).join('\n ') +// } +// } +// +// class UndefinedParameterTypeError extends CucumberExpressionError { +// constructor(typeName) { +// super(`Undefined parameter type {${typeName}}`) +// } +// } +// +// module.exports = { +// AmbiguousParameterTypeError, +// UndefinedParameterTypeError, +// CucumberExpressionError, +// } diff --git a/cucumber-expressions/go/parameter_type_matcher.go b/cucumber-expressions/go/parameter_type_matcher.go index 1ebcf7faee..aac4c681cf 100644 --- a/cucumber-expressions/go/parameter_type_matcher.go +++ b/cucumber-expressions/go/parameter_type_matcher.go @@ -44,21 +44,6 @@ func (p *ParameterTypeMatcher) Group() string { return p.text[p.matchPosition:][p.match[0]:p.match[1]] } -type ParameterTypeSlice []*ParameterType - -func (p ParameterTypeSlice) Len() int { - return len(p) -} -func (p ParameterTypeSlice) Less(i, j int) bool { - posComparison := p[i].UseForSnippets() - return p[i] < p[j] -} -func (p ParameterTypeSlice) Swap(i, j int) { - p[i], p[j] = p[j], p[i] - } - -func []ParameterType(a, b) - // static compare(a, b) { // const posComparison = a.start - b.start // if (posComparison !== 0) return posComparison diff --git a/cucumber-expressions/go/regular_expression.go b/cucumber-expressions/go/regular_expression.go index 1df51fd0fa..51183f68dd 100644 --- a/cucumber-expressions/go/regular_expression.go +++ b/cucumber-expressions/go/regular_expression.go @@ -1 +1,58 @@ package cucumberexpressions + +import ( + "fmt" + "regexp" +) + +type RegularExpression struct { + expressionRegexp *regexp.Regexp + parameterTypeRegistry *ParameterTypeRegistry + treeRegexp *TreeRegexp +} + +func NewRegularExpression(expressionRegexp *regexp.Regexp, parameterTypeRegistry *ParameterTypeRegistry) *RegularExpression { + return &RegularExpression{ + expressionRegexp: expressionRegexp, + parameterTypeRegistry: parameterTypeRegistry, + treeRegexp: NewTreeRegexp(expressionRegexp), + } +} + +func (r *RegularExpression) Match(text string) ([]*Argument, error) { + parameterTypes := []*ParameterType{} + for _, groupBuilder := range r.treeRegexp.GroupBuilder().Children() { + parameterTypeRegexp := groupBuilder.Source() + parameterType, err := r.parameterTypeRegistry.LookupByRegexp(parameterTypeRegexp) + if err != nil { + return nil, err + } + if parameterType == nil { + parameterType, err = NewParameterType( + parameterTypeRegexp, + []*regexp.Regexp{regexp.MustCompile(parameterTypeRegexp)}, + "string", + func(arg3 ...string) interface{} { + return arg3[0] + }, + false, + false, + ) + if err != nil { + return nil, err + } + } + parameterTypes = append(parameterTypes, parameterType) + } + args := BuildArguments(r.treeRegexp, text, parameterTypes) + fmt.Printf("%#v\n", args) + return args, nil +} + +func (r *RegularExpression) Regexp() *regexp.Regexp { + return r.expressionRegexp +} + +func (r *RegularExpression) Source() string { + return r.expressionRegexp.String() +} diff --git a/cucumber-expressions/go/regular_expression_test.go b/cucumber-expressions/go/regular_expression_test.go index fe2be8e5fc..2ea4bba393 100644 --- a/cucumber-expressions/go/regular_expression_test.go +++ b/cucumber-expressions/go/regular_expression_test.go @@ -1 +1,91 @@ package cucumberexpressions_test + +import ( + "regexp" + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestRegularExpression(t *testing.T) { + t.Run("documents match arguments", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + + /// [capture-match-arguments] + expr := regexp.MustCompile(`I have (\d+) cukes? in my (\w+) now`) + expression := cucumberexpressions.NewRegularExpression(expr, parameterTypeRegistry) + args, err := expression.Match("I have 7 cukes in my belly now") + require.NoError(t, err) + require.Equal(t, args[0].GetValue(), 7) + require.Equal(t, args[1].GetValue(), "belly") + /// [capture-match-arguments] + }) + + t.Run("does no transform by default", func(t *testing.T) { + require.Equal(t, Match(t, `(\d\d)`, "22")[0], "22") + }) + + t.Run("transforms negative int", func(t *testing.T) { + require.Equal(t, Match(t, `(-?\d+)`, "-22")[0], -22) + }) + + t.Run("transforms positive int", func(t *testing.T) { + require.Equal(t, Match(t, `(-?\d+)`, "22")[0], 22) + }) + + t.Run("transforms float without integer part", func(t *testing.T) { + require.Equal(t, Match(t, `(-?\d*\.\d+)`, ".22")[0], 0.22) + }) + + t.Run("transforms float with sign", func(t *testing.T) { + require.Equal(t, Match(t, `(-?\d*\.\d+)`, "-1.22")[0], -1.22) + }) + + t.Run("returns empty array when no match float with sign", func(t *testing.T) { + require.Empty(t, Match(t, "hello", "world")) + }) + + t.Run("ignores non capturing groups", func(t *testing.T) { + require.Equal( + t, + Match( + t, + `(\S+) ?(can|cannot)? (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?`, + "I can cancel the 1st slide upload", + ), + []interface{}{"I", "can", 1, "slide"}, + ) + }) + + t.Run("works with escaped parenthesis", func(t *testing.T) { + require.Empty(t, Match(t, `Across the line\(s\)`, "Across the line(s)")) + }) + + t.Run("exposes regexp and source", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expr := regexp.MustCompile(`I have (\d+) cukes? in my (\w+) now`) + expression := cucumberexpressions.NewRegularExpression(expr, parameterTypeRegistry) + require.Equal(t, expression.Regexp(), expr) + require.Equal(t, expression.Source(), expr.String()) + }) + +} + +func Match(t *testing.T, expr, text string) []interface{} { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expression := cucumberexpressions.NewRegularExpression(regexp.MustCompile(expr), parameterTypeRegistry) + args, err := expression.Match(text) + require.NoError(t, err) + if len(args) == 0 { + return []interface{}{} + } + result := make([]interface{}, len(args)) + for i, arg := range args { + result[i] = arg.GetValue() + } + return result +} From 7f422fcd4658d5528d67d0bd2988011dfcfb799e Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 21 Mar 2018 18:52:16 -0700 Subject: [PATCH 09/26] wip --- .../go/cucumber_expression_generator.go | 14 ++ .../go/cucumber_expression_generator_test.go | 209 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 cucumber-expressions/go/cucumber_expression_generator_test.go diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go index ac1820ddad..b9abd7d00f 100644 --- a/cucumber-expressions/go/cucumber_expression_generator.go +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -1,5 +1,19 @@ package cucumberexpressions +type CucumberExpressionGenerator struct { + parameterTypeRegistry *ParameterTypeRegistry +} + +func NewCucumberExpressionGenerator(parameterTypeRegistry *ParameterTypeRegistry) *CucumberExpressionGenerator { + return &CucumberExpressionGenerator{ + parameterTypeRegistry: parameterTypeRegistry, + } +} + +func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*GeneratedExpression { + return []*GeneratedExpression{} +} + // const util = require('util') // const ParameterTypeMatcher = require('./parameter_type_matcher') // const ParameterType = require('./parameter_type') diff --git a/cucumber-expressions/go/cucumber_expression_generator_test.go b/cucumber-expressions/go/cucumber_expression_generator_test.go new file mode 100644 index 0000000000..5311e98b0a --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_generator_test.go @@ -0,0 +1,209 @@ +package cucumberexpressions_test + +import ( + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestCucumberExpressionGeneratory(t *testing.T) { + t.Run("documents expression generation", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + + /// [generate-expression] + generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) + undefinedStepText := "I have 2 cucumbers and 1.5 tomato" + generatedExpression := generator.GenerateExpressions(undefinedStepText)[0] + require.Equal(t, generatedExpression.Source(), "I have {int} cucumbers and {float} tomato") + require.Equal(t, generatedExpression.ParameterNames()[0], "int") + require.Equal(t, generatedExpression.ParameterTypes()[1].Name(), "float") + /// [generate-expression] + }) +} + +// /* eslint-env mocha */ +// const assert = require('assert') +// const CucumberExpressionGenerator = require('../src/cucumber_expression_generator') +// const ParameterType = require('../src/parameter_type') +// const ParameterTypeRegistry = require('../src/parameter_type_registry') +// +// class Currency {} +// +// describe('CucumberExpressionGenerator', () => { +// let parameterTypeRegistry, generator +// +// function assertExpression(expectedExpression, expectedArgumentNames, text) { +// const generatedExpression = generator.generateExpressions(text)[0] +// assert.deepEqual(generatedExpression.parameterNames, expectedArgumentNames) +// assert.equal(generatedExpression.source, expectedExpression) +// } +// +// beforeEach(() => { +// parameterTypeRegistry = new ParameterTypeRegistry() +// generator = new CucumberExpressionGenerator(parameterTypeRegistry) +// }) +// +// it('documents expression generation', () => { +// const parameterRegistry = new ParameterTypeRegistry() +// /// [generate-expression] +// const generator = new CucumberExpressionGenerator(parameterRegistry) +// const undefinedStepText = 'I have 2 cucumbers and 1.5 tomato' +// const generatedExpression = generator.generateExpressions( +// undefinedStepText +// )[0] +// assert.equal( +// generatedExpression.source, +// 'I have {int} cucumbers and {float} tomato' +// ) +// assert.equal(generatedExpression.parameterNames[0], 'int') +// assert.equal(generatedExpression.parameterTypes[1].name, 'float') +// /// [generate-expression] +// }) +// +// it('generates expression for no args', () => { +// assertExpression('hello', [], 'hello') +// }) +// +// it('generates expression for int float arg', () => { +// assertExpression( +// 'I have {int} cukes and {float} euro', +// ['int', 'float'], +// 'I have 2 cukes and 1.5 euro' +// ) +// }) +// +// it('generates expression for strings', () => { +// assertExpression('I am {int}%% foobar', ['int'], 'I am 20%% foobar') +// }) +// +// it('generates expression for strings with % sign', () => { +// assertExpression( +// 'I like {string} and {string}', +// ['string', 'string2'], +// 'I like "bangers" and \'mash\'' +// ) +// }) +// +// it('generates expression for just int', () => { +// assertExpression('{int}', ['int'], '99999') +// }) +// +// it('numbers only second argument when builtin type is not reserved keyword', () => { +// assertExpression( +// 'I have {float} cukes and {float} euro', +// ['float', 'float2'], +// 'I have 2.5 cukes and 1.5 euro' +// ) +// }) +// +// it('generates expression for custom type', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'currency', +// /[A-Z]{3}/, +// Currency, +// s => new Currency(s), +// true, +// false +// ) +// ) +// +// assertExpression( +// 'I have a {currency} account', +// ['currency'], +// 'I have a EUR account' +// ) +// }) +// +// it('prefers leftmost match when there is overlap', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'currency', +// /cd/, +// Currency, +// s => new Currency(s), +// true, +// false +// ) +// ) +// parameterTypeRegistry.defineParameterType( +// new ParameterType('date', /bc/, Date, s => new Date(s), true, false) +// ) +// +// assertExpression('a{date}defg', ['date'], 'abcdefg') +// }) +// +// // TODO: prefers widest match +// +// it('generates all combinations of expressions when several parameter types match', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'currency', +// /x/, +// null, +// s => new Currency(s), +// true, +// false +// ) +// ) +// parameterTypeRegistry.defineParameterType( +// new ParameterType('date', /x/, null, s => new Date(s), true, false) +// ) +// +// const generatedExpressions = generator.generateExpressions( +// 'I have x and x and another x' +// ) +// const expressions = generatedExpressions.map(e => e.source) +// assert.deepEqual(expressions, [ +// 'I have {currency} and {currency} and another {currency}', +// 'I have {currency} and {currency} and another {date}', +// 'I have {currency} and {date} and another {currency}', +// 'I have {currency} and {date} and another {date}', +// 'I have {date} and {currency} and another {currency}', +// 'I have {date} and {currency} and another {date}', +// 'I have {date} and {date} and another {currency}', +// 'I have {date} and {date} and another {date}', +// ]) +// }) +// +// it('exposes parameter type names in generated expression', () => { +// const expression = generator.generateExpressions( +// 'I have 2 cukes and 1.5 euro' +// )[0] +// const typeNames = expression.parameterTypes.map(parameter => parameter.name) +// assert.deepEqual(typeNames, ['int', 'float']) +// }) +// +// it('ignores parameter types with optional capture groups', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'optional-flight', +// /(1st flight)?/, +// null, +// s => s, +// true, +// false +// ) +// ) +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'optional-hotel', +// /(1st hotel)?/, +// null, +// s => s, +// true, +// false +// ) +// ) +// +// const expression = generator.generateExpressions( +// 'I reach Stage4: 1st flight-1st hotl' +// )[0] +// assert.equal( +// expression.source, +// 'I reach Stage{int}: {int}st flight{int}st hotl' +// ) +// }) +// }) From ef6499faf77ea4f103f133ab3089ab5ad8b7b0d5 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Thu, 22 Mar 2018 11:45:45 -0700 Subject: [PATCH 10/26] update --- .../go/cucumber_expression_generator.go | 86 +++- .../go/cucumber_expression_generator_test.go | 383 +++++++++--------- .../go/generated_expression.go | 6 +- cucumber-expressions/go/parameter_type.go | 16 + .../go/parameter_type_matcher.go | 12 + .../go/parameter_type_registry.go | 10 +- cucumber-expressions/go/regular_expression.go | 5 +- 7 files changed, 319 insertions(+), 199 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go index b9abd7d00f..30d1a97922 100644 --- a/cucumber-expressions/go/cucumber_expression_generator.go +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -1,5 +1,10 @@ package cucumberexpressions +import ( + "sort" + "strings" +) + type CucumberExpressionGenerator struct { parameterTypeRegistry *ParameterTypeRegistry } @@ -11,7 +16,86 @@ func NewCucumberExpressionGenerator(parameterTypeRegistry *ParameterTypeRegistry } func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*GeneratedExpression { - return []*GeneratedExpression{} + parameterTypeCombinations := [][]*ParameterType{} + parameterTypeMatchers := c.createParameterTypeMatchers(text) + expressionTemplate := "" + pos := 0 + + for { + matchingParameterTypeMatchers := []*ParameterTypeMatcher{} + for _, parameterTypeMatcher := range parameterTypeMatchers { + advancedParameterTypeMatcher := parameterTypeMatcher.AdvanceTo(pos) + if advancedParameterTypeMatcher.Find() { + matchingParameterTypeMatchers = append(matchingParameterTypeMatchers, advancedParameterTypeMatcher) + } + } + if len(matchingParameterTypeMatchers) > 0 { + sort.Slice(matchingParameterTypeMatchers, func(i int, j int) bool { + return CompareParameterTypeMatchers(matchingParameterTypeMatchers[i], matchingParameterTypeMatchers[j]) <= 0 + }) + + // Find all the best parameter type matchers, they are all candidates. + bestParameterTypeMatcher := matchingParameterTypeMatchers[0] + bestParameterTypeMatchers := []*ParameterTypeMatcher{} + for _, parameterTypeMatcher := range matchingParameterTypeMatchers { + if CompareParameterTypeMatchers(parameterTypeMatcher, bestParameterTypeMatcher) == 0 { + bestParameterTypeMatchers = append(bestParameterTypeMatchers, parameterTypeMatcher) + } + } + + // Build a list of parameter types without duplicates. The reason there + // might be duplicates is that some parameter types have more than one regexp, + // which means multiple ParameterTypeMatcher objects will have a reference to the + // same ParameterType. + // We're sorting the list so preferential parameter types are listed first. + // Users are most likely to want these, so they should be listed at the top. + parameterTypesMap := map[*ParameterType]bool{} + for _, parameterTypeMatcher := range bestParameterTypeMatchers { + parameterTypesMap[parameterTypeMatcher.parameterType] = true + } + parameterTypes := []*ParameterType{} + for parameterType := range parameterTypesMap { + parameterTypes = append(parameterTypes, parameterType) + } + sort.Slice(parameterTypes, func(i int, j int) bool { + return CompareParameterTypes(parameterTypes[i], parameterTypes[j]) <= 0 + }) + + parameterTypeCombinations = append(parameterTypeCombinations, parameterTypes) + expressionTemplate += escapeForUtilFormat(text[pos:bestParameterTypeMatcher.Start()]) + "{%s}" + pos = bestParameterTypeMatcher.Start() + len(bestParameterTypeMatcher.Group()) + } else { + break + } + + if pos > len(text) { + break + } + } + expressionTemplate += escapeForUtilFormat(text[pos:]) + return NewCombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).GenerateExpressions() +} + +func (c *CucumberExpressionGenerator) createParameterTypeMatchers(text string) []*ParameterTypeMatcher { + result := []*ParameterTypeMatcher{} + for _, parameterType := range c.parameterTypeRegistry.ParamaterTypes() { + if parameterType.UseForSnippets() { + result = append(result, c.createParameterTypeMatchers2(parameterType, text)...) + } + } + return result +} + +func (c *CucumberExpressionGenerator) createParameterTypeMatchers2(parameterType *ParameterType, text string) []*ParameterTypeMatcher { + result := make([]*ParameterTypeMatcher, len(parameterType.Regexps())) + for i, r := range parameterType.Regexps() { + result[i] = NewParameterTypeMatcher(parameterType, r, text, 0) + } + return result +} + +func escapeForUtilFormat(s string) string { + return strings.Replace(s, "%", "%%", -1) } // const util = require('util') diff --git a/cucumber-expressions/go/cucumber_expression_generator_test.go b/cucumber-expressions/go/cucumber_expression_generator_test.go index 5311e98b0a..0d534a2ec4 100644 --- a/cucumber-expressions/go/cucumber_expression_generator_test.go +++ b/cucumber-expressions/go/cucumber_expression_generator_test.go @@ -1,12 +1,17 @@ package cucumberexpressions_test import ( + "regexp" "testing" cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" "github.com/stretchr/testify/require" ) +type Currency struct { + ISO4217 string +} + func TestCucumberExpressionGeneratory(t *testing.T) { t.Run("documents expression generation", func(t *testing.T) { parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() @@ -21,189 +26,199 @@ func TestCucumberExpressionGeneratory(t *testing.T) { require.Equal(t, generatedExpression.ParameterTypes()[1].Name(), "float") /// [generate-expression] }) + + t.Run("generates expression for no args", func(t *testing.T) { + assertExpression(t, "hello", []string{}, "hello") + }) + + t.Run("generates expression for int float arg", func(t *testing.T) { + assertExpression( + t, + "I have {int} cukes and {float} euro", + []string{"int", "float"}, + "I have 2 cukes and 1.5 euro", + ) + }) + + t.Run("generates expression for strings with % sign", func(t *testing.T) { + assertExpression(t, "I am {int}%% foobar", []string{"int"}, "I am 20%% foobar") + }) + + t.Run("generates expression for int float arg", func(t *testing.T) { + assertExpression( + t, + "I like {string} and {string}", + []string{"string", "string2"}, + `I like "bangers" and 'mash'`, + ) + }) + + t.Run("generates expression for just int", func(t *testing.T) { + assertExpression(t, "{int}", []string{"int"}, "99999") + }) + + t.Run("numbers only second argument when builtin type is not reserved keyword", func(t *testing.T) { + assertExpression( + t, + "I have {float} cukes and {float} euro", + []string{"float", "float2"}, + "I have 2.5 cukes and 1.5 euro", + ) + }) + + t.Run("generates expression for custom type", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + currencyParameterType, err := cucumberexpressions.NewParameterType( + "currency", + []*regexp.Regexp{regexp.MustCompile("[A-Z]{3}")}, + "Currency", + func(arg3 ...string) interface{} { + return Currency{ISO4217: arg3[0]} + }, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(currencyParameterType) + + assertExpressionWithParameterTypeRegistry( + t, + parameterTypeRegistry, + "I have a {currency} account", + []string{"currency"}, + "I have a EUR account", + ) + }) + + t.Run("prefers leftmost match when there is overlap", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + parameterType1, err := cucumberexpressions.NewParameterType( + "type1", + []*regexp.Regexp{regexp.MustCompile("cd")}, + "type1", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(parameterType1) + parameterType2, err := cucumberexpressions.NewParameterType( + "type2", + []*regexp.Regexp{regexp.MustCompile("bc")}, + "type2", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(parameterType2) + + assertExpressionWithParameterTypeRegistry( + t, + parameterTypeRegistry, + "a{type2}defg", + []string{"type2"}, + "abcdefg", + ) + }) + + // TODO: prefers widest match + + t.Run("generates all combinations of expressions when several parameter types match", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + parameterType1, err := cucumberexpressions.NewParameterType( + "type1", + []*regexp.Regexp{regexp.MustCompile("x")}, + "type1", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(parameterType1) + parameterType2, err := cucumberexpressions.NewParameterType( + "type2", + []*regexp.Regexp{regexp.MustCompile("x")}, + "type2", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(parameterType2) + generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) + generatedExpressions := generator.GenerateExpressions("I have x and x and another x") + sources := make([]string, len(generatedExpressions)) + for i, generatedExpression := range generatedExpressions { + sources[i] = generatedExpression.Source() + } + require.Equal(t, sources, []string{ + "I have {type1} and {type1} and another {type1}", + "I have {type1} and {type1} and another {type2}", + "I have {type1} and {type2} and another {type1}", + "I have {type1} and {type2} and another {type2}", + "I have {type2} and {type1} and another {type1}", + "I have {type2} and {type1} and another {type2}", + "I have {type2} and {type2} and another {type1}", + "I have {type2} and {type2} and another {type2}", + }) + }) + + t.Run("exposes parameter type names in generated expression", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) + generatedExpression := generator.GenerateExpressions("I have 2 cukes and 1.5 euro")[0] + typeNames := make([]string, len(generatedExpression.ParameterTypes())) + for i, parameterType := range generatedExpression.ParameterTypes() { + typeNames[i] = parameterType.Name() + } + require.Equal(t, typeNames, []string{"int", "float"}) + }) + + t.Run("ignores parameter types with optional capture groups", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + optionalFlightParameterType, err := cucumberexpressions.NewParameterType( + "optional-flight", + []*regexp.Regexp{regexp.MustCompile("(1st flight)?")}, + "optional-flight", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(optionalFlightParameterType) + optionalHotelParameterType, err := cucumberexpressions.NewParameterType( + "optional-hotel", + []*regexp.Regexp{regexp.MustCompile("(1st hotel)?")}, + "optional-hotel", + nil, + true, + false, + ) + require.NoError(t, err) + parameterTypeRegistry.DefineParameterType(optionalHotelParameterType) + generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) + generatedExpression := generator.GenerateExpressions("I reach Stage4: 1st flight-1st hotel")[0] + require.Equal(t, generatedExpression.Source(), "I reach Stage{int}: {int}st flight{int}st hotel") + }) } -// /* eslint-env mocha */ -// const assert = require('assert') -// const CucumberExpressionGenerator = require('../src/cucumber_expression_generator') -// const ParameterType = require('../src/parameter_type') -// const ParameterTypeRegistry = require('../src/parameter_type_registry') -// -// class Currency {} -// -// describe('CucumberExpressionGenerator', () => { -// let parameterTypeRegistry, generator -// -// function assertExpression(expectedExpression, expectedArgumentNames, text) { -// const generatedExpression = generator.generateExpressions(text)[0] -// assert.deepEqual(generatedExpression.parameterNames, expectedArgumentNames) -// assert.equal(generatedExpression.source, expectedExpression) -// } -// -// beforeEach(() => { -// parameterTypeRegistry = new ParameterTypeRegistry() -// generator = new CucumberExpressionGenerator(parameterTypeRegistry) -// }) -// -// it('documents expression generation', () => { -// const parameterRegistry = new ParameterTypeRegistry() -// /// [generate-expression] -// const generator = new CucumberExpressionGenerator(parameterRegistry) -// const undefinedStepText = 'I have 2 cucumbers and 1.5 tomato' -// const generatedExpression = generator.generateExpressions( -// undefinedStepText -// )[0] -// assert.equal( -// generatedExpression.source, -// 'I have {int} cucumbers and {float} tomato' -// ) -// assert.equal(generatedExpression.parameterNames[0], 'int') -// assert.equal(generatedExpression.parameterTypes[1].name, 'float') -// /// [generate-expression] -// }) -// -// it('generates expression for no args', () => { -// assertExpression('hello', [], 'hello') -// }) -// -// it('generates expression for int float arg', () => { -// assertExpression( -// 'I have {int} cukes and {float} euro', -// ['int', 'float'], -// 'I have 2 cukes and 1.5 euro' -// ) -// }) -// -// it('generates expression for strings', () => { -// assertExpression('I am {int}%% foobar', ['int'], 'I am 20%% foobar') -// }) -// -// it('generates expression for strings with % sign', () => { -// assertExpression( -// 'I like {string} and {string}', -// ['string', 'string2'], -// 'I like "bangers" and \'mash\'' -// ) -// }) -// -// it('generates expression for just int', () => { -// assertExpression('{int}', ['int'], '99999') -// }) -// -// it('numbers only second argument when builtin type is not reserved keyword', () => { -// assertExpression( -// 'I have {float} cukes and {float} euro', -// ['float', 'float2'], -// 'I have 2.5 cukes and 1.5 euro' -// ) -// }) -// -// it('generates expression for custom type', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'currency', -// /[A-Z]{3}/, -// Currency, -// s => new Currency(s), -// true, -// false -// ) -// ) -// -// assertExpression( -// 'I have a {currency} account', -// ['currency'], -// 'I have a EUR account' -// ) -// }) -// -// it('prefers leftmost match when there is overlap', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'currency', -// /cd/, -// Currency, -// s => new Currency(s), -// true, -// false -// ) -// ) -// parameterTypeRegistry.defineParameterType( -// new ParameterType('date', /bc/, Date, s => new Date(s), true, false) -// ) -// -// assertExpression('a{date}defg', ['date'], 'abcdefg') -// }) -// -// // TODO: prefers widest match -// -// it('generates all combinations of expressions when several parameter types match', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'currency', -// /x/, -// null, -// s => new Currency(s), -// true, -// false -// ) -// ) -// parameterTypeRegistry.defineParameterType( -// new ParameterType('date', /x/, null, s => new Date(s), true, false) -// ) -// -// const generatedExpressions = generator.generateExpressions( -// 'I have x and x and another x' -// ) -// const expressions = generatedExpressions.map(e => e.source) -// assert.deepEqual(expressions, [ -// 'I have {currency} and {currency} and another {currency}', -// 'I have {currency} and {currency} and another {date}', -// 'I have {currency} and {date} and another {currency}', -// 'I have {currency} and {date} and another {date}', -// 'I have {date} and {currency} and another {currency}', -// 'I have {date} and {currency} and another {date}', -// 'I have {date} and {date} and another {currency}', -// 'I have {date} and {date} and another {date}', -// ]) -// }) -// -// it('exposes parameter type names in generated expression', () => { -// const expression = generator.generateExpressions( -// 'I have 2 cukes and 1.5 euro' -// )[0] -// const typeNames = expression.parameterTypes.map(parameter => parameter.name) -// assert.deepEqual(typeNames, ['int', 'float']) -// }) -// -// it('ignores parameter types with optional capture groups', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'optional-flight', -// /(1st flight)?/, -// null, -// s => s, -// true, -// false -// ) -// ) -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'optional-hotel', -// /(1st hotel)?/, -// null, -// s => s, -// true, -// false -// ) -// ) -// -// const expression = generator.generateExpressions( -// 'I reach Stage4: 1st flight-1st hotl' -// )[0] -// assert.equal( -// expression.source, -// 'I reach Stage{int}: {int}st flight{int}st hotl' -// ) -// }) -// }) +func assertExpression(t *testing.T, expectedExpression string, expectedArgumentNames []string, text string) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + assertExpressionWithParameterTypeRegistry(t, parameterTypeRegistry, expectedExpression, expectedArgumentNames, text) +} + +func assertExpressionWithParameterTypeRegistry(t *testing.T, parameterTypeRegistry *cucumberexpressions.ParameterTypeRegistry, expectedExpression string, expectedArgumentNames []string, text string) { + generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) + generatedExpressions := generator.GenerateExpressions(text) + require.Len(t, generatedExpressions, 1) + generatedExpression := generatedExpressions[0] + require.Equal(t, generatedExpression.ParameterNames(), expectedArgumentNames) + require.Equal(t, generatedExpression.Source(), expectedExpression) +} diff --git a/cucumber-expressions/go/generated_expression.go b/cucumber-expressions/go/generated_expression.go index ceacc8cefd..8b6bdca658 100644 --- a/cucumber-expressions/go/generated_expression.go +++ b/cucumber-expressions/go/generated_expression.go @@ -38,9 +38,11 @@ func (g *GeneratedExpression) ParameterTypes() []*ParameterType { func getParameterName(typeName string, usageByTypeName map[string]int) string { count, ok := usageByTypeName[typeName] if !ok { - count = 0 + count = 1 + } else { + count++ } - usageByTypeName[typeName] = count + 1 + usageByTypeName[typeName] = count if count == 1 { return typeName } diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index 9e96bb3e78..b3d590ac50 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -51,3 +51,19 @@ func (p *ParameterType) PreferForRegexpMatch() bool { func (p *ParameterType) Transform(groupValues []string) interface{} { return p.transform(groupValues...) } + +func CompareParameterTypes(pt1, pt2 *ParameterType) int { + if pt1.PreferForRegexpMatch() && pt2.PreferForRegexpMatch() { + return -1 + } + if pt2.PreferForRegexpMatch() && !pt1.PreferForRegexpMatch() { + return 1 + } + if pt1.Name() < pt2.Name() { + return -1 + } + if pt1.Name() > pt2.Name() { + return 1 + } + return 0 +} diff --git a/cucumber-expressions/go/parameter_type_matcher.go b/cucumber-expressions/go/parameter_type_matcher.go index aac4c681cf..535f85c776 100644 --- a/cucumber-expressions/go/parameter_type_matcher.go +++ b/cucumber-expressions/go/parameter_type_matcher.go @@ -44,6 +44,18 @@ func (p *ParameterTypeMatcher) Group() string { return p.text[p.matchPosition:][p.match[0]:p.match[1]] } +func CompareParameterTypeMatchers(a, b *ParameterTypeMatcher) int { + posComparison := a.Start() - b.Start() + if posComparison != 0 { + return posComparison + } + lengthComparison := len(b.Group()) - len(a.Group()) + if lengthComparison != 0 { + return lengthComparison + } + return 0 +} + // static compare(a, b) { // const posComparison = a.start - b.start // if (posComparison !== 0) return posComparison diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go index 3bca04b2e0..d720c06ccc 100644 --- a/cucumber-expressions/go/parameter_type_registry.go +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -98,7 +98,7 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { return result, nil } -func (p *ParameterTypeRegistry) GetParamaterTypes() []*ParameterType { +func (p *ParameterTypeRegistry) ParamaterTypes() []*ParameterType { result := make([]*ParameterType, len(p.parameterTypeByName)) index := 0 for _, parameterType := range p.parameterTypeByName { @@ -149,13 +149,7 @@ func (p *ParameterTypeRegistry) DefineParameterType(parameterType *ParameterType } parameterTypes = append(parameterTypes, parameterType) sort.Slice(parameterTypes, func(i int, j int) bool { - if parameterTypes[i].PreferForRegexpMatch() && !parameterTypes[j].PreferForRegexpMatch() { - return true - } - if parameterTypes[j].PreferForRegexpMatch() && !parameterTypes[i].PreferForRegexpMatch() { - return false - } - return parameterTypes[i].Name() < parameterTypes[j].Name() + return CompareParameterTypes(parameterTypes[i], parameterTypes[j]) <= 0 }) p.parameterTypesByRegexp[parameterTypeRegexp.String()] = parameterTypes } diff --git a/cucumber-expressions/go/regular_expression.go b/cucumber-expressions/go/regular_expression.go index 51183f68dd..8bd898a670 100644 --- a/cucumber-expressions/go/regular_expression.go +++ b/cucumber-expressions/go/regular_expression.go @@ -1,7 +1,6 @@ package cucumberexpressions import ( - "fmt" "regexp" ) @@ -44,9 +43,7 @@ func (r *RegularExpression) Match(text string) ([]*Argument, error) { } parameterTypes = append(parameterTypes, parameterType) } - args := BuildArguments(r.treeRegexp, text, parameterTypes) - fmt.Printf("%#v\n", args) - return args, nil + return BuildArguments(r.treeRegexp, text, parameterTypes), nil } func (r *RegularExpression) Regexp() *regexp.Regexp { From cc22e4837f476e3728e3c7faaaa021a1baa27e55 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 11:00:12 -0700 Subject: [PATCH 11/26] wip --- cucumber-expressions/go/argument.go | 2 +- .../go/cucumber_expression.go | 97 ++++++++- .../go/cucumber_expression_test.go | 202 ++++++++++++++++++ cucumber-expressions/go/errors.go | 24 ++- .../go/parameter_type_registry.go | 19 +- 5 files changed, 318 insertions(+), 26 deletions(-) create mode 100644 cucumber-expressions/go/cucumber_expression_test.go diff --git a/cucumber-expressions/go/argument.go b/cucumber-expressions/go/argument.go index efff01d184..2601b2f142 100644 --- a/cucumber-expressions/go/argument.go +++ b/cucumber-expressions/go/argument.go @@ -10,7 +10,7 @@ type Argument struct { func BuildArguments(treeRegexp *TreeRegexp, text string, parameterTypes []*ParameterType) []*Argument { group := treeRegexp.Match(text) if group == nil { - return []*Argument{} + return nil } argGroups := group.Children() if len(argGroups) != len(parameterTypes) { diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 5dbc5a7d38..6ceb4dbf35 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -1,5 +1,91 @@ package cucumberexpressions +import ( + "fmt" + "regexp" + "strings" +) + +type CucumberExpression struct { + expression string + parameterTypes []*ParameterType + treeRegexp *TreeRegexp +} + +func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (*CucumberExpression, error) { + ESCAPE_REGEXP := regexp.MustCompile(`([\\^[$.|?*+])`) + PARAMETER_REGEXP := regexp.MustCompile("{([^}]+)}") + OPTIONAL_REGEXP := regexp.MustCompile(`(\\\\)?\([^)]+\)`) + ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP := regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`) + + result := &CucumberExpression{expression: expression} + parameterTypes := []*ParameterType{} + r := "^" + matchOffset := 0 + + // Does not include (){} because they have special meaning + fmt.Println(expression) + expression = ESCAPE_REGEXP.ReplaceAllString(expression, "\\$1") + fmt.Println(expression) + + // Create non-capturing, optional capture groups from parenthesis + expression = OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + if strings.HasPrefix(match, "\\\\") { + return fmt.Sprintf(`\(%s\)`, match[3:len(match)-1]) + } + return fmt.Sprintf("(?:%s", match[1:]) + }) + + expression = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + return fmt.Sprintf("(?:%s)", strings.Replace(match, "/", "|", -1)) + }) + + matches := PARAMETER_REGEXP.FindAllStringSubmatchIndex(expression[matchOffset:], -1) + for _, indicies := range matches { + typeName := expression[indicies[2]:indicies[3]] + parameterType := parameterTypeRegistry.LookupByTypeName(typeName) + if parameterType == nil { + return nil, NewUndefinedParameterTypeError(typeName) + } + parameterTypes = append(parameterTypes, parameterType) + text := expression[matchOffset:indicies[0]] + captureRegexp := buildCaptureRegexp(parameterType.regexps) + matchOffset = indicies[1] + r += text + r += captureRegexp + } + + r += expression[matchOffset:] + "$" + result.parameterTypes = parameterTypes + result.treeRegexp = NewTreeRegexp(regexp.MustCompile(r)) + return result, nil +} + +func (c *CucumberExpression) Match(text string) []*Argument { + return BuildArguments(c.treeRegexp, text, c.parameterTypes) +} + +func (c *CucumberExpression) Regexp() *regexp.Regexp { + return c.treeRegexp.Regexp() +} + +func (c *CucumberExpression) Source() string { + return c.expression +} + +func buildCaptureRegexp(regexps []*regexp.Regexp) string { + if len(regexps) == 1 { + return fmt.Sprintf("(%s)", regexps[0].String()) + } + + captureGroups := make([]string, len(regexps)) + for i, r := range regexps { + captureGroups[i] = fmt.Sprintf("(?:%s)", r.String()) + } + + return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) +} + // const Argument = require('./argument') // const TreeRegexp = require('./tree_regexp') // const { UndefinedParameterTypeError } = require('./errors') @@ -55,17 +141,6 @@ package cucumberexpressions // this._treeRegexp = new TreeRegexp(regexp) // } // -// match(text) { -// return Argument.build(this._treeRegexp, text, this._parameterTypes) -// } -// -// get regexp() { -// return this._treeRegexp.regexp -// } -// -// get source() { -// return this._expression -// } // } // // function buildCaptureRegexp(regexps) { diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go new file mode 100644 index 0000000000..99667b039c --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -0,0 +1,202 @@ +package cucumberexpressions_test + +import ( + "fmt" + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestCucumberExpression(t *testing.T) { + t.Run("documents expression generation", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + + /// [capture-match-arguments] + expr := "I have {int} cuke(s)" + expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + args := expression.Match("I have 7 cukes") + require.Equal(t, args[0].GetValue(), 7) + /// [capture-match-arguments] + }) + + t.Run("matches word", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {word} mice", "three blind mice"), + []interface{}{"blind"}, + ) + }) + + t.Run("matches double quoted string", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three "blind" mice`), + []interface{}{"blind"}, + ) + }) + + t.Run("matches multiple double quoted strings", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} and {string} mice", `three "blind" and "crippled" mice`), + []interface{}{"blind", "crippled"}, + ) + }) + + t.Run("matches single quoted string", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three 'blind' mice`), + []interface{}{"blind"}, + ) + }) + + t.Run("matches multiple single quoted strings", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} and {string} mice", `three 'blind' and 'crippled' mice`), + []interface{}{"blind", "crippled"}, + ) + }) + + t.Run("does not match misquoted string", func(t *testing.T) { + require.Nil( + t, + MatchCucumberExpression(t, "three {string} mice", `three "blind' mice`), + ) + }) + + t.Run("matches single quoted strings with double quotes", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three '"blind"' mice`), + []interface{}{`"blind"`}, + ) + }) + + t.Run("matches double quoted strings with single quotes", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three "'blind'" mice`), + []interface{}{`'blind'`}, + ) + }) + + t.Run("matches double quoted string with escaped double quote", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three "bl\"nd" mice`), + []interface{}{`bl\"nd`}, + ) + }) + + t.Run("matches single quoted string with escaped single quote", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three {string} mice", `three 'bl\'nd' mice`), + []interface{}{`bl\'nd`}, + ) + }) + + t.Run("matches escaped parenthesis", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "three \\\\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`), + []interface{}{"blind"}, + ) + }) + + t.Run("doesn't match float as int", func(t *testing.T) { + require.Nil( + t, + MatchCucumberExpression(t, "{int}", "1.22"), + ) + }) + + t.Run("matches float", func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression(t, "{float}", "0.22"), + []interface{}{0.22}, + ) + require.Equal( + t, + MatchCucumberExpression(t, "{float}", ".22"), + []interface{}{0.22}, + ) + }) + + t.Run("returns error for unknown parameter float", func(t *testing.T) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + _, err = cucumberexpressions.NewCucumberExpression("{unknown}", parameterTypeRegistry) + require.Error(t, err) + require.Equal(t, err.Error(), "Undefined parameter type {unknown}") + }) + + t.Run("exposes source", func(t *testing.T) { + expr := "I have {int} cuke(s)" + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + require.Equal(t, expression.Source(), expr) + }) + + t.Run("escapes special characters", func(t *testing.T) { + for _, char := range []string{"\\", "[", "]", "^", "$", ".", "|", "?", "*", "+"} { + t.Run(fmt.Sprintf("escapes %s", char), func(t *testing.T) { + require.Equal( + t, + MatchCucumberExpression( + t, + fmt.Sprintf("I have {int} cuke(s) and %s", char), + fmt.Sprintf("I have 800 cukes and %s", char), + ), + []interface{}{800}, + ) + }) + } + + t.Run("escapes .", func(t *testing.T) { + expr := "I have {int} cuke(s) and ." + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + require.Nil(t, expression.Match("I have 800 cukes and 3")) + require.NotNil(t, expression.Match("I have 800 cukes and .")) + }) + + t.Run("escapes |", func(t *testing.T) { + expr := "I have {int} cuke(s) and a|b" + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + require.Nil(t, expression.Match("I have 800 cukes and a")) + require.Nil(t, expression.Match("I have 800 cukes and b")) + require.NotNil(t, expression.Match("I have 800 cukes and a|b")) + }) + }) +} + +func MatchCucumberExpression(t *testing.T, expr string, text string) []interface{} { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) + require.NoError(t, err) + args := expression.Match(text) + require.NoError(t, err) + if args == nil { + return nil + } + result := make([]interface{}, len(args)) + for i, arg := range args { + result[i] = arg.GetValue() + } + return result +} diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index b5edd7c8ba..c29684677d 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -2,18 +2,22 @@ package cucumberexpressions import "fmt" -func NewCucumberExpressionError(text string) error { - return &CucumberExpressionError{s: text} -} - type CucumberExpressionError struct { s string } +func NewCucumberExpressionError(text string) error { + return &CucumberExpressionError{s: text} +} + func (e *CucumberExpressionError) Error() string { return e.s } +type AmbiguousParameterTypeError struct { + s string +} + func NewAmbiguousParameterTypeErrorForConstructor(keyName, keyValue string, parameterTypes []*ParameterType, generatedExpressions []string) error { return &AmbiguousParameterTypeError{ s: fmt.Sprintf( @@ -26,11 +30,19 @@ func NewAmbiguousParameterTypeErrorForConstructor(keyName, keyValue string, para } } -type AmbiguousParameterTypeError struct { +func (e *AmbiguousParameterTypeError) Error() string { + return e.s +} + +type UndefinedParameterTypeError struct { s string } -func (e *AmbiguousParameterTypeError) Error() string { +func NewUndefinedParameterTypeError(typeName string) error { + return &UndefinedParameterTypeError{s: fmt.Sprintf("Undefined parameter type {%s}", typeName)} +} + +func (e *UndefinedParameterTypeError) Error() string { return e.s } diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go index d720c06ccc..fe90077f91 100644 --- a/cucumber-expressions/go/parameter_type_registry.go +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -35,8 +35,8 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { "int", INTEGER_REGEXPS, "int", - func(arg3 ...string) interface{} { - i, err := strconv.Atoi(arg3[0]) + func(args ...string) interface{} { + i, err := strconv.Atoi(args[0]) if err != nil { panic(err) } @@ -53,8 +53,8 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { "float", FLOAT_REGEXPS, "float", - func(arg3 ...string) interface{} { - f, err := strconv.ParseFloat(arg3[0], 64) + func(args ...string) interface{} { + f, err := strconv.ParseFloat(args[0], 64) if err != nil { panic(err) } @@ -71,8 +71,8 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { "word", WORD_REGEXPS, "string", - func(arg3 ...string) interface{} { - return arg3[0] + func(args ...string) interface{} { + return args[0] }, false, false, @@ -85,8 +85,11 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { "string", STRING_REGEXPS, "string", - func(arg3 ...string) interface{} { - return arg3[0] + func(args ...string) interface{} { + if args[0] == "" && args[1] != "" { + return args[1] + } + return args[0] }, true, false, From 96c09d86afe194dc9cbefc88acb341940f634804 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 14:17:25 -0700 Subject: [PATCH 12/26] update --- cucumber-expressions/go/cucumber_expression.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 6ceb4dbf35..f94962d310 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -15,7 +15,7 @@ type CucumberExpression struct { func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (*CucumberExpression, error) { ESCAPE_REGEXP := regexp.MustCompile(`([\\^[$.|?*+])`) PARAMETER_REGEXP := regexp.MustCompile("{([^}]+)}") - OPTIONAL_REGEXP := regexp.MustCompile(`(\\\\)?\([^)]+\)`) + OPTIONAL_REGEXP := regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP := regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`) result := &CucumberExpression{expression: expression} @@ -24,14 +24,12 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy matchOffset := 0 // Does not include (){} because they have special meaning - fmt.Println(expression) expression = ESCAPE_REGEXP.ReplaceAllString(expression, "\\$1") - fmt.Println(expression) // Create non-capturing, optional capture groups from parenthesis expression = OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { - if strings.HasPrefix(match, "\\\\") { - return fmt.Sprintf(`\(%s\)`, match[3:len(match)-1]) + if strings.HasPrefix(match, "\\\\\\\\") { + return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1]) } return fmt.Sprintf("(?:%s", match[1:]) }) From 4138ca6c4b0de25724bf9bfd2e32ff07388b0a17 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 14:27:08 -0700 Subject: [PATCH 13/26] update --- .../go/cucumber_expression.go | 2 +- .../go/cucumber_expression_regexp_test.go | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 cucumber-expressions/go/cucumber_expression_regexp_test.go diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index f94962d310..98c4d48bf6 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -31,7 +31,7 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy if strings.HasPrefix(match, "\\\\\\\\") { return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1]) } - return fmt.Sprintf("(?:%s", match[1:]) + return fmt.Sprintf("(?:%s)?", match[1:len(match)-1]) }) expression = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go new file mode 100644 index 0000000000..bcb54d962e --- /dev/null +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -0,0 +1,58 @@ +package cucumberexpressions_test + +import ( + "testing" + + cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + "github.com/stretchr/testify/require" +) + +func TestCucumberExpressionRegExpTranslation(t *testing.T) { + t.Run("translates no arguments", func(t *testing.T) { + assertRegexp( + t, + "I have 10 cukes in my belly now", + "^I have 10 cukes in my belly now$", + ) + }) + + t.Run("translates alternation", func(t *testing.T) { + assertRegexp( + t, + "I had/have a great/nice/charming friend", + "^I (?:had|have) a (?:great|nice|charming) friend$", + ) + }) + + t.Run("translates alternation with non-alpha", func(t *testing.T) { + assertRegexp( + t, + "I said Alpha1/Beta1", + "^I said (?:Alpha1|Beta1)$", + ) + }) + + t.Run("translates parameters", func(t *testing.T) { + assertRegexp( + t, + "I have {float} cukes at {int} o'clock", + `^I have (-?\d*\.\d+) cukes at ((?:-?\d+)|(?:\d+)) o'clock$`, + ) + }) + + t.Run("translates parenthesis to non-capturing optional capture group", func(t *testing.T) { + assertRegexp( + t, + "I have many big(ish) cukes", + `^I have many big(?:ish)? cukes$`, + ) + }) +} + +func assertRegexp(t *testing.T, expression string, expectedRegexp string) { + parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() + require.NoError(t, err) + generator, err := cucumberexpressions.NewCucumberExpression(expression, parameterTypeRegistry) + require.NoError(t, err) + require.Equal(t, generator.Regexp().String(), expectedRegexp) +} From 3182552e2072e58fa79446a584e243ab3a74a2c7 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 14:54:52 -0700 Subject: [PATCH 14/26] update --- cucumber-expressions/go/Makefile | 16 +++++++ ...orial_generated_expression_factory_test.go | 2 +- .../go/cucumber_expression_generator_test.go | 2 +- .../go/cucumber_expression_regexp_test.go | 2 +- .../go/cucumber_expression_test.go | 2 +- cucumber-expressions/go/lib/.gitignore | 2 + cucumber-expressions/go/parameter_type.go | 14 +++++- .../go/parameter_type_test.go | 45 +++++++++++++++++++ .../go/regular_expression_test.go | 2 +- cucumber-expressions/go/tree_regexp_test.go | 2 +- 10 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 cucumber-expressions/go/Makefile create mode 100644 cucumber-expressions/go/lib/.gitignore create mode 100644 cucumber-expressions/go/parameter_type_test.go diff --git a/cucumber-expressions/go/Makefile b/cucumber-expressions/go/Makefile new file mode 100644 index 0000000000..34adaf988e --- /dev/null +++ b/cucumber-expressions/go/Makefile @@ -0,0 +1,16 @@ +SHELL := /usr/bin/env bash +export GOPATH = $(realpath ./lib) + +default: test +.PHONY: default + +test: lib/src/github.com/stretchr/testify + go test +.PHONY: clean + +lib/src/github.com/stretchr/testify: + go get github.com/stretchr/testify + +clean: + rm -rf lib/src lib/pkg +.PHONY: clean diff --git a/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go b/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go index 840d437a44..1d27659eba 100644 --- a/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go +++ b/cucumber-expressions/go/combinatorial_generated_expression_factory_test.go @@ -4,7 +4,7 @@ import ( "regexp" "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) diff --git a/cucumber-expressions/go/cucumber_expression_generator_test.go b/cucumber-expressions/go/cucumber_expression_generator_test.go index 0d534a2ec4..7869d2c30d 100644 --- a/cucumber-expressions/go/cucumber_expression_generator_test.go +++ b/cucumber-expressions/go/cucumber_expression_generator_test.go @@ -4,7 +4,7 @@ import ( "regexp" "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go index bcb54d962e..77b01e8a03 100644 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -3,7 +3,7 @@ package cucumberexpressions_test import ( "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 99667b039c..ad1d64b955 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) diff --git a/cucumber-expressions/go/lib/.gitignore b/cucumber-expressions/go/lib/.gitignore new file mode 100644 index 0000000000..f34a96c51a --- /dev/null +++ b/cucumber-expressions/go/lib/.gitignore @@ -0,0 +1,2 @@ +pkg +src diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index b3d590ac50..428d9ccb6a 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -1,6 +1,11 @@ package cucumberexpressions -import "regexp" +import ( + "errors" + "regexp" +) + +var HAS_FLAG_REGEKP = regexp.MustCompile(`\(\?[imsU-]+(\:.*)?\)`) type ParameterType struct { name string @@ -14,7 +19,12 @@ type ParameterType struct { func NewParameterType(name string, regexps []*regexp.Regexp, type1 string, transform func(...string) interface{}, useForSnippets bool, preferForRegexpMatch bool) (*ParameterType, error) { if transform == nil { transform = func(s ...string) interface{} { - return s + return s[0] + } + } + for _, r := range regexps { + if HAS_FLAG_REGEKP.MatchString(r.String()) { + return nil, errors.New("ParameterType Regexps can't use flags") } } // TODO error if uses flags diff --git a/cucumber-expressions/go/parameter_type_test.go b/cucumber-expressions/go/parameter_type_test.go new file mode 100644 index 0000000000..613d1ad488 --- /dev/null +++ b/cucumber-expressions/go/parameter_type_test.go @@ -0,0 +1,45 @@ +package cucumberexpressions_test + +import ( + "regexp" + "testing" + + cucumberexpressions "." + "github.com/stretchr/testify/require" +) + +func TestParameterType(t *testing.T) { + t.Run("does not allow ignore flag on regexp", func(t *testing.T) { + _, err := cucumberexpressions.NewParameterType( + "case-insensitive", + []*regexp.Regexp{regexp.MustCompile("(?i)[a-z]+")}, + "case-insensitive", + nil, + true, + true, + ) + require.EqualError(t, err, "ParameterType Regexps can't use flags") + }) + +} + +// /* eslint-env mocha */ +// const assertThrows = require('./assert_throws') +// const ParameterType = require('../src/parameter_type') +// +// describe('ParameterType', () => { +// it('does not allow ignore flag on regexp', () => { +// assertThrows( +// () => +// new ParameterType( +// 'case-insensitive', +// /[a-z]+/i, +// String, +// s => s, +// true, +// true +// ), +// "ParameterType Regexps can't use flag 'i'" +// ) +// }) +// }) diff --git a/cucumber-expressions/go/regular_expression_test.go b/cucumber-expressions/go/regular_expression_test.go index 2ea4bba393..0b23290530 100644 --- a/cucumber-expressions/go/regular_expression_test.go +++ b/cucumber-expressions/go/regular_expression_test.go @@ -4,7 +4,7 @@ import ( "regexp" "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) diff --git a/cucumber-expressions/go/tree_regexp_test.go b/cucumber-expressions/go/tree_regexp_test.go index be7f71d4fc..a8cd5df71e 100644 --- a/cucumber-expressions/go/tree_regexp_test.go +++ b/cucumber-expressions/go/tree_regexp_test.go @@ -4,7 +4,7 @@ import ( "regexp" "testing" - cucumberexpressions "github.com/cucumber/cucumber/cucumber-expressions/go" + cucumberexpressions "." "github.com/stretchr/testify/require" ) From b2ec3421064fee942d48b7b828b4874632ee50db Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 15:38:51 -0700 Subject: [PATCH 15/26] update --- cucumber-expressions/go/Makefile | 2 +- .../go/cucumber_expression_generator_test.go | 21 ++--- .../go/cucumber_expression_regexp_test.go | 3 +- .../go/cucumber_expression_test.go | 20 ++-- cucumber-expressions/go/parameter_type.go | 2 +- .../go/parameter_type_matcher.go | 8 -- .../go/parameter_type_registry.go | 14 +-- .../go/parameter_type_registry_test.go | 93 +++++++++++++++++++ .../go/parameter_type_test.go | 22 ----- cucumber-expressions/go/regular_expression.go | 2 +- .../go/regular_expression_test.go | 9 +- 11 files changed, 121 insertions(+), 75 deletions(-) create mode 100644 cucumber-expressions/go/parameter_type_registry_test.go diff --git a/cucumber-expressions/go/Makefile b/cucumber-expressions/go/Makefile index 34adaf988e..f8b919656c 100644 --- a/cucumber-expressions/go/Makefile +++ b/cucumber-expressions/go/Makefile @@ -5,7 +5,7 @@ default: test .PHONY: default test: lib/src/github.com/stretchr/testify - go test + go test ${ARGS} .PHONY: clean lib/src/github.com/stretchr/testify: diff --git a/cucumber-expressions/go/cucumber_expression_generator_test.go b/cucumber-expressions/go/cucumber_expression_generator_test.go index 7869d2c30d..ba385c7a73 100644 --- a/cucumber-expressions/go/cucumber_expression_generator_test.go +++ b/cucumber-expressions/go/cucumber_expression_generator_test.go @@ -14,8 +14,7 @@ type Currency struct { func TestCucumberExpressionGeneratory(t *testing.T) { t.Run("documents expression generation", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() /// [generate-expression] generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) @@ -67,8 +66,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { }) t.Run("generates expression for custom type", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() currencyParameterType, err := cucumberexpressions.NewParameterType( "currency", []*regexp.Regexp{regexp.MustCompile("[A-Z]{3}")}, @@ -92,8 +90,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { }) t.Run("prefers leftmost match when there is overlap", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() parameterType1, err := cucumberexpressions.NewParameterType( "type1", []*regexp.Regexp{regexp.MustCompile("cd")}, @@ -127,8 +124,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { // TODO: prefers widest match t.Run("generates all combinations of expressions when several parameter types match", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() parameterType1, err := cucumberexpressions.NewParameterType( "type1", []*regexp.Regexp{regexp.MustCompile("x")}, @@ -168,8 +164,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { }) t.Run("exposes parameter type names in generated expression", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() generator := cucumberexpressions.NewCucumberExpressionGenerator(parameterTypeRegistry) generatedExpression := generator.GenerateExpressions("I have 2 cukes and 1.5 euro")[0] typeNames := make([]string, len(generatedExpression.ParameterTypes())) @@ -180,8 +175,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { }) t.Run("ignores parameter types with optional capture groups", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() optionalFlightParameterType, err := cucumberexpressions.NewParameterType( "optional-flight", []*regexp.Regexp{regexp.MustCompile("(1st flight)?")}, @@ -209,8 +203,7 @@ func TestCucumberExpressionGeneratory(t *testing.T) { } func assertExpression(t *testing.T, expectedExpression string, expectedArgumentNames []string, text string) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() assertExpressionWithParameterTypeRegistry(t, parameterTypeRegistry, expectedExpression, expectedArgumentNames, text) } diff --git a/cucumber-expressions/go/cucumber_expression_regexp_test.go b/cucumber-expressions/go/cucumber_expression_regexp_test.go index 77b01e8a03..29ad12b025 100644 --- a/cucumber-expressions/go/cucumber_expression_regexp_test.go +++ b/cucumber-expressions/go/cucumber_expression_regexp_test.go @@ -50,8 +50,7 @@ func TestCucumberExpressionRegExpTranslation(t *testing.T) { } func assertRegexp(t *testing.T, expression string, expectedRegexp string) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() generator, err := cucumberexpressions.NewCucumberExpression(expression, parameterTypeRegistry) require.NoError(t, err) require.Equal(t, generator.Regexp().String(), expectedRegexp) diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index ad1d64b955..0fdce45f51 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -10,8 +10,7 @@ import ( func TestCucumberExpression(t *testing.T) { t.Run("documents expression generation", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() /// [capture-match-arguments] expr := "I have {int} cuke(s)" @@ -130,17 +129,15 @@ func TestCucumberExpression(t *testing.T) { }) t.Run("returns error for unknown parameter float", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) - _, err = cucumberexpressions.NewCucumberExpression("{unknown}", parameterTypeRegistry) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + _, err := cucumberexpressions.NewCucumberExpression("{unknown}", parameterTypeRegistry) require.Error(t, err) require.Equal(t, err.Error(), "Undefined parameter type {unknown}") }) t.Run("exposes source", func(t *testing.T) { expr := "I have {int} cuke(s)" - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) require.Equal(t, expression.Source(), expr) @@ -163,8 +160,7 @@ func TestCucumberExpression(t *testing.T) { t.Run("escapes .", func(t *testing.T) { expr := "I have {int} cuke(s) and ." - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) require.Nil(t, expression.Match("I have 800 cukes and 3")) @@ -173,8 +169,7 @@ func TestCucumberExpression(t *testing.T) { t.Run("escapes |", func(t *testing.T) { expr := "I have {int} cuke(s) and a|b" - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) require.Nil(t, expression.Match("I have 800 cukes and a")) @@ -185,8 +180,7 @@ func TestCucumberExpression(t *testing.T) { } func MatchCucumberExpression(t *testing.T, expr string, text string) []interface{} { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) args := expression.Match(text) diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index 428d9ccb6a..24798caa86 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -63,7 +63,7 @@ func (p *ParameterType) Transform(groupValues []string) interface{} { } func CompareParameterTypes(pt1, pt2 *ParameterType) int { - if pt1.PreferForRegexpMatch() && pt2.PreferForRegexpMatch() { + if pt1.PreferForRegexpMatch() && !pt2.PreferForRegexpMatch() { return -1 } if pt2.PreferForRegexpMatch() && !pt1.PreferForRegexpMatch() { diff --git a/cucumber-expressions/go/parameter_type_matcher.go b/cucumber-expressions/go/parameter_type_matcher.go index 535f85c776..57b5377691 100644 --- a/cucumber-expressions/go/parameter_type_matcher.go +++ b/cucumber-expressions/go/parameter_type_matcher.go @@ -55,11 +55,3 @@ func CompareParameterTypeMatchers(a, b *ParameterTypeMatcher) int { } return 0 } - -// static compare(a, b) { -// const posComparison = a.start - b.start -// if (posComparison !== 0) return posComparison -// const lengthComparison = b.group.length - a.group.length -// if (lengthComparison !== 0) return lengthComparison -// return 0 -// } diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go index fe90077f91..3a2601b55e 100644 --- a/cucumber-expressions/go/parameter_type_registry.go +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -26,7 +26,7 @@ type ParameterTypeRegistry struct { parameterTypesByRegexp map[string][]*ParameterType } -func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { +func NewParameterTypeRegistry() *ParameterTypeRegistry { result := &ParameterTypeRegistry{ parameterTypeByName: map[string]*ParameterType{}, parameterTypesByRegexp: map[string][]*ParameterType{}, @@ -46,7 +46,7 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { true, ) if err != nil { - return nil, err + panic(err) } result.DefineParameterType(intParameterType) floatParameterType, err := NewParameterType( @@ -64,7 +64,7 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { false, ) if err != nil { - return nil, err + panic(err) } result.DefineParameterType(floatParameterType) wordParameterType, err := NewParameterType( @@ -78,7 +78,7 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { false, ) if err != nil { - return nil, err + panic(err) } result.DefineParameterType(wordParameterType) stringParameterType, err := NewParameterType( @@ -95,10 +95,10 @@ func NewParameterTypeRegistry() (*ParameterTypeRegistry, error) { false, ) if err != nil { - return nil, err + panic(err) } result.DefineParameterType(stringParameterType) - return result, nil + return result } func (p *ParameterTypeRegistry) ParamaterTypes() []*ParameterType { @@ -115,7 +115,7 @@ func (p *ParameterTypeRegistry) LookupByTypeName(name string) *ParameterType { return p.parameterTypeByName[name] } -func (p *ParameterTypeRegistry) LookupByRegexp(parameterTypeRegexp string) (*ParameterType, error) { +func (p *ParameterTypeRegistry) LookupByRegexp(parameterTypeRegexp string, expressionRegexp string, text string) (*ParameterType, error) { parameterTypes, ok := p.parameterTypesByRegexp[parameterTypeRegexp] if !ok { return nil, nil diff --git a/cucumber-expressions/go/parameter_type_registry_test.go b/cucumber-expressions/go/parameter_type_registry_test.go new file mode 100644 index 0000000000..0134efbd86 --- /dev/null +++ b/cucumber-expressions/go/parameter_type_registry_test.go @@ -0,0 +1,93 @@ +package cucumberexpressions_test + +import ( + "fmt" + "regexp" + "testing" + + cucumberexpressions "." + "github.com/stretchr/testify/require" +) + +var CAPITALISED_WORD_REGEXPS = []*regexp.Regexp{ + regexp.MustCompile(`[A-Z]+\w+`), +} + +func TestParameterTypeRegistry(t *testing.T) { + t.Run("does not allow more than one preferential parameter type for each regexp", func(t *testing.T) { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + nameParameterType, err := cucumberexpressions.NewParameterType( + "name", + CAPITALISED_WORD_REGEXPS, + "name", + nil, + true, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(nameParameterType) + require.NoError(t, err) + personParameterType, err := cucumberexpressions.NewParameterType( + "person", + CAPITALISED_WORD_REGEXPS, + "person", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(personParameterType) + require.NoError(t, err) + placeParameterType, err := cucumberexpressions.NewParameterType( + "place", + CAPITALISED_WORD_REGEXPS, + "place", + nil, + true, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(placeParameterType) + require.EqualError(t, err, fmt.Sprintf("There can only be one preferential parameter type per regexp. The regexp /%s/ is used for two preferential parameter types, {name} and {place}", CAPITALISED_WORD_REGEXPS[0].String())) + }) + + t.Run("looks up preferential parameter type by regexp", func(t *testing.T) { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + nameParameterType, err := cucumberexpressions.NewParameterType( + "name", + CAPITALISED_WORD_REGEXPS, + "name", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(nameParameterType) + require.NoError(t, err) + personParameterType, err := cucumberexpressions.NewParameterType( + "person", + CAPITALISED_WORD_REGEXPS, + "person", + nil, + true, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(personParameterType) + require.NoError(t, err) + placeParameterType, err := cucumberexpressions.NewParameterType( + "place", + CAPITALISED_WORD_REGEXPS, + "place", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(placeParameterType) + require.NoError(t, err) + matchingParameterType, err := parameterTypeRegistry.LookupByRegexp(`[A-Z]+\w+`, `([A-Z]+\w+) and ([A-Z]+\w+)`, "Lisa and Bob") + require.NoError(t, err) + require.Equal(t, matchingParameterType, personParameterType) + }) +} diff --git a/cucumber-expressions/go/parameter_type_test.go b/cucumber-expressions/go/parameter_type_test.go index 613d1ad488..c6e48e5787 100644 --- a/cucumber-expressions/go/parameter_type_test.go +++ b/cucumber-expressions/go/parameter_type_test.go @@ -20,26 +20,4 @@ func TestParameterType(t *testing.T) { ) require.EqualError(t, err, "ParameterType Regexps can't use flags") }) - } - -// /* eslint-env mocha */ -// const assertThrows = require('./assert_throws') -// const ParameterType = require('../src/parameter_type') -// -// describe('ParameterType', () => { -// it('does not allow ignore flag on regexp', () => { -// assertThrows( -// () => -// new ParameterType( -// 'case-insensitive', -// /[a-z]+/i, -// String, -// s => s, -// true, -// true -// ), -// "ParameterType Regexps can't use flag 'i'" -// ) -// }) -// }) diff --git a/cucumber-expressions/go/regular_expression.go b/cucumber-expressions/go/regular_expression.go index 8bd898a670..8b7ea2e49e 100644 --- a/cucumber-expressions/go/regular_expression.go +++ b/cucumber-expressions/go/regular_expression.go @@ -22,7 +22,7 @@ func (r *RegularExpression) Match(text string) ([]*Argument, error) { parameterTypes := []*ParameterType{} for _, groupBuilder := range r.treeRegexp.GroupBuilder().Children() { parameterTypeRegexp := groupBuilder.Source() - parameterType, err := r.parameterTypeRegistry.LookupByRegexp(parameterTypeRegexp) + parameterType, err := r.parameterTypeRegistry.LookupByRegexp(parameterTypeRegexp, r.expressionRegexp.String(), text) if err != nil { return nil, err } diff --git a/cucumber-expressions/go/regular_expression_test.go b/cucumber-expressions/go/regular_expression_test.go index 0b23290530..66dda1c51e 100644 --- a/cucumber-expressions/go/regular_expression_test.go +++ b/cucumber-expressions/go/regular_expression_test.go @@ -10,8 +10,7 @@ import ( func TestRegularExpression(t *testing.T) { t.Run("documents match arguments", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() /// [capture-match-arguments] expr := regexp.MustCompile(`I have (\d+) cukes? in my (\w+) now`) @@ -64,8 +63,7 @@ func TestRegularExpression(t *testing.T) { }) t.Run("exposes regexp and source", func(t *testing.T) { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expr := regexp.MustCompile(`I have (\d+) cukes? in my (\w+) now`) expression := cucumberexpressions.NewRegularExpression(expr, parameterTypeRegistry) require.Equal(t, expression.Regexp(), expr) @@ -75,8 +73,7 @@ func TestRegularExpression(t *testing.T) { } func Match(t *testing.T, expr, text string) []interface{} { - parameterTypeRegistry, err := cucumberexpressions.NewParameterTypeRegistry() - require.NoError(t, err) + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression := cucumberexpressions.NewRegularExpression(regexp.MustCompile(expr), parameterTypeRegistry) args, err := expression.Match(text) require.NoError(t, err) From 2b1eaa7810d594eb8fd22cdc49f72d1085ae8781 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 16:37:14 -0700 Subject: [PATCH 16/26] update --- cucumber-expressions/go/errors.go | 36 ++++++++--- .../go/parameter_type_registry.go | 14 +---- .../go/parameter_type_registry_test.go | 63 +++++++++++++++++++ 3 files changed, 94 insertions(+), 19 deletions(-) diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index c29684677d..28a38147a3 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -1,6 +1,9 @@ package cucumberexpressions -import "fmt" +import ( + "fmt" + "strings" +) type CucumberExpressionError struct { s string @@ -18,14 +21,33 @@ type AmbiguousParameterTypeError struct { s string } -func NewAmbiguousParameterTypeErrorForConstructor(keyName, keyValue string, parameterTypes []*ParameterType, generatedExpressions []string) error { +func NewAmbiguousParameterTypeError(parameterTypeRegexp, expressionRegexp string, parameterTypes []*ParameterType, generatedExpressions []*GeneratedExpression) error { + parameterTypeNames := make([]string, len(parameterTypes)) + for i, parameterType := range parameterTypes { + parameterTypeNames[i] = "{" + parameterType.Name() + "}" + } + generatedExpressionSources := make([]string, len(generatedExpressions)) + for i, generatedExpression := range generatedExpressions { + generatedExpressionSources[i] = generatedExpression.Source() + } return &AmbiguousParameterTypeError{ s: fmt.Sprintf( - "parameter type with %s=%s is used by several parameter types: %v, %v", - keyName, - keyValue, - parameterTypes, - generatedExpressions, + `Your Regular Expression /%s/ +matches multiple parameter types with regexp /%s/: + %s + +I couldn't decide which one to use. You have two options: + +1) Use a Cucumber Expression instead of a Regular Expression. Try one of these: + %s + +2) Make one of the parameter types preferential and continue to use a Regular Expression. + +`, + expressionRegexp, + parameterTypeRegexp, + strings.Join(parameterTypeNames, "\n "), + strings.Join(generatedExpressionSources, "\n "), ), } } diff --git a/cucumber-expressions/go/parameter_type_registry.go b/cucumber-expressions/go/parameter_type_registry.go index 3a2601b55e..7275e75d2c 100644 --- a/cucumber-expressions/go/parameter_type_registry.go +++ b/cucumber-expressions/go/parameter_type_registry.go @@ -121,18 +121,8 @@ func (p *ParameterTypeRegistry) LookupByRegexp(parameterTypeRegexp string, expre return nil, nil } if len(parameterTypes) > 1 && !parameterTypes[0].PreferForRegexpMatch() { - // We don't do this check on insertion because we only want to restrict - // ambiguiuty when we look up by Regexp. Users of CucumberExpression should - // not be restricted. - // const generatedExpressions = new CucumberExpressionGenerator( - // this - // ).generateExpressions(text) - // throw new AmbiguousParameterTypeError.forRegExp( - // parameterTypeRegexp, - // expressionRegexp, - // parameterTypes, - // generatedExpressions - // ) + generatedExpressions := NewCucumberExpressionGenerator(p).GenerateExpressions(text) + return nil, NewAmbiguousParameterTypeError(parameterTypeRegexp, expressionRegexp, parameterTypes, generatedExpressions) } return parameterTypes[0], nil } diff --git a/cucumber-expressions/go/parameter_type_registry_test.go b/cucumber-expressions/go/parameter_type_registry_test.go index 0134efbd86..ce28087722 100644 --- a/cucumber-expressions/go/parameter_type_registry_test.go +++ b/cucumber-expressions/go/parameter_type_registry_test.go @@ -90,4 +90,67 @@ func TestParameterTypeRegistry(t *testing.T) { require.NoError(t, err) require.Equal(t, matchingParameterType, personParameterType) }) + + t.Run("throws ambiguous exception on lookup when no parameter types are preferential", func(t *testing.T) { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + nameParameterType, err := cucumberexpressions.NewParameterType( + "name", + CAPITALISED_WORD_REGEXPS, + "name", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(nameParameterType) + require.NoError(t, err) + personParameterType, err := cucumberexpressions.NewParameterType( + "person", + CAPITALISED_WORD_REGEXPS, + "person", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(personParameterType) + require.NoError(t, err) + placeParameterType, err := cucumberexpressions.NewParameterType( + "place", + CAPITALISED_WORD_REGEXPS, + "place", + nil, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(placeParameterType) + require.NoError(t, err) + _, err = parameterTypeRegistry.LookupByRegexp(`[A-Z]+\w+`, `([A-Z]+\w+) and ([A-Z]+\w+)`, "Lisa and Bob") + require.EqualError( + t, + err, + "Your Regular Expression /([A-Z]+\\w+) and ([A-Z]+\\w+)/\n"+ + "matches multiple parameter types with regexp /[A-Z]+\\w+/:\n"+ + " {name}\n"+ + " {person}\n"+ + " {place}\n"+ + "\n"+ + "I couldn't decide which one to use. You have two options:\n"+ + "\n"+ + "1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:\n"+ + " {name} and {name}\n"+ + " {name} and {person}\n"+ + " {name} and {place}\n"+ + " {person} and {name}\n"+ + " {person} and {person}\n"+ + " {person} and {place}\n"+ + " {place} and {name}\n"+ + " {place} and {person}\n"+ + " {place} and {place}\n"+ + "\n"+ + "2) Make one of the parameter types preferential and continue to use a Regular Expression.\n"+ + "\n", + ) + }) } From 6bc47d9f636c324c06260ead20ebc1ebabe7a934 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 17:13:32 -0700 Subject: [PATCH 17/26] update --- cucumber-expressions/go/examples.txt | 19 +++++++ .../go/expression_examples_test.go | 54 +++++++++++++++++++ cucumber-expressions/go/regular_expression.go | 2 +- 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 cucumber-expressions/go/examples.txt create mode 100644 cucumber-expressions/go/expression_examples_test.go diff --git a/cucumber-expressions/go/examples.txt b/cucumber-expressions/go/examples.txt new file mode 100644 index 0000000000..317e7ff514 --- /dev/null +++ b/cucumber-expressions/go/examples.txt @@ -0,0 +1,19 @@ +I have {int} cuke(s) +I have 22 cukes +[22] +--- +I have {int} cuke(s) and some \[]^$.|?*+ +I have 1 cuke and some \[]^$.|?*+ +[1] +--- +/I have (\d+) cukes? in my (\w+) now/ +I have 22 cukes in my belly now +[22,"belly"] +--- +/I have (-?\d+) cukes? in my (.*) now/ +I have 1 cuke in my belly now +[1,"belly"] +--- +/^Something( with an optional argument)?$/ +Something +[""] diff --git a/cucumber-expressions/go/expression_examples_test.go b/cucumber-expressions/go/expression_examples_test.go new file mode 100644 index 0000000000..789595ee8d --- /dev/null +++ b/cucumber-expressions/go/expression_examples_test.go @@ -0,0 +1,54 @@ +package cucumberexpressions_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "regexp" + "strings" + "testing" + + cucumberexpressions "." + "github.com/stretchr/testify/require" +) + +func TestExamples(t *testing.T) { + examples, err := ioutil.ReadFile("./examples.txt") + require.NoError(t, err) + + chunks := strings.Split(string(examples), "---") + for _, chunk := range chunks { + lines := strings.Split(strings.TrimSpace(chunk), "\n") + expressionText, text, expectedArgs := lines[0], lines[1], lines[2] + t.Run(fmt.Sprintf("works with %s", expressionText), func(t *testing.T) { + args := MatchExample(t, expressionText, text) + argsJson, err := json.Marshal(args) + require.NoError(t, err) + require.Equal(t, expectedArgs, string(argsJson)) + }) + } +} + +func MatchExample(t *testing.T, expressionText, text string) []interface{} { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + var args []*cucumberexpressions.Argument + var err error + if strings.HasPrefix(expressionText, "/") { + r := regexp.MustCompile(expressionText[1 : len(expressionText)-1]) + expression := cucumberexpressions.NewRegularExpression(r, parameterTypeRegistry) + args, err = expression.Match(text) + require.NoError(t, err) + } else { + expression, err := cucumberexpressions.NewCucumberExpression(expressionText, parameterTypeRegistry) + require.NoError(t, err) + args = expression.Match(text) + } + if args == nil { + return nil + } + result := make([]interface{}, len(args)) + for i, arg := range args { + result[i] = arg.GetValue() + } + return result +} diff --git a/cucumber-expressions/go/regular_expression.go b/cucumber-expressions/go/regular_expression.go index 8b7ea2e49e..2ef49a7764 100644 --- a/cucumber-expressions/go/regular_expression.go +++ b/cucumber-expressions/go/regular_expression.go @@ -38,7 +38,7 @@ func (r *RegularExpression) Match(text string) ([]*Argument, error) { false, ) if err != nil { - return nil, err + panic(err) } } parameterTypes = append(parameterTypes, parameterType) From 3e3752bb34f75ce936e5fb492dbee7e986fd9e28 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Fri, 23 Mar 2018 17:36:53 -0700 Subject: [PATCH 18/26] update --- .../go/custom_parameter_type_test.go | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 cucumber-expressions/go/custom_parameter_type_test.go diff --git a/cucumber-expressions/go/custom_parameter_type_test.go b/cucumber-expressions/go/custom_parameter_type_test.go new file mode 100644 index 0000000000..ab7ed55eec --- /dev/null +++ b/cucumber-expressions/go/custom_parameter_type_test.go @@ -0,0 +1,248 @@ +package cucumberexpressions_test + +import ( + "regexp" + "strconv" + "testing" + + cucumberexpressions "." + "github.com/stretchr/testify/require" +) + +/// [color-constructor] +type Color struct { + name string +} + +/// [color-constructor] + +type CSSColor struct { + name string +} + +func CreateParameterTypeRegistry(t *testing.T) *cucumberexpressions.ParameterTypeRegistry { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + /// [add-color-parameter-type] + colorParameterType, err := cucumberexpressions.NewParameterType( + "color", // name + []*regexp.Regexp{regexp.MustCompile("red|blue|yellow")}, // regexps + "color", // type + func(args ...string) interface{} { return &Color{name: args[0]} }, // transformer + false, // useForSnippets + true, // preferForRegexpMatch + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + /// [add-color-parameter-type] + require.NoError(t, err) + return parameterTypeRegistry +} + +func TestCustomParameterTypes(t *testing.T) { + t.Run("CucumberExpression", func(t *testing.T) { + t.Run("matches parameters with custom parameter type", func(t *testing.T) { + parameterTypeRegistry := CreateParameterTypeRegistry(t) + expression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) + require.NoError(t, err) + value := expression.Match("I have a red ball")[0].GetValue() + require.Equal(t, &Color{name: "red"}, value) + }) + + t.Run("matches parameters with multiple capture groups", func(t *testing.T) { + type Coordinate struct { + x int + y int + z int + } + parameterTypeRegistry := CreateParameterTypeRegistry(t) + coordinateParameterType, err := cucumberexpressions.NewParameterType( + "coordinate", + []*regexp.Regexp{regexp.MustCompile(`(\d+),\s*(\d+),\s*(\d+)`)}, + "coordinate", + func(args ...string) interface{} { + x, err := strconv.Atoi(args[0]) + if err != nil { + panic(err) + } + y, err := strconv.Atoi(args[1]) + if err != nil { + panic(err) + } + z, err := strconv.Atoi(args[2]) + if err != nil { + panic(err) + } + return &Coordinate{x: x, y: y, z: z} + }, + true, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(coordinateParameterType) + expression, err := cucumberexpressions.NewCucumberExpression("A {int} thick line from {coordinate} to {coordinate}", parameterTypeRegistry) + args := expression.Match("A 5 thick line from 10,20,30 to 40,50,60") + require.Equal(t, 5, args[0].GetValue()) + require.Equal(t, &Coordinate{x: 10, y: 20, z: 30}, args[1].GetValue()) + require.Equal(t, &Coordinate{x: 40, y: 50, z: 60}, args[2].GetValue()) + }) + }) +} + +// +// describe('CucumberExpression', () => { +// it('matches parameters with custom parameter type using optional capture group', () => { +// parameterTypeRegistry = new ParameterTypeRegistry() +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'color', +// [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/], +// Color, +// s => new Color(s), +// false, +// true +// ) +// ) +// const expression = new CucumberExpression( +// 'I have a {color} ball', +// parameterTypeRegistry +// ) +// const value = expression.match('I have a dark red ball')[0].getValue(null) +// assert.equal(value.name, 'dark red') +// }) +// +// it('defers transformation until queried from argument', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'throwing', +// /bad/, +// null, +// s => { +// throw new Error(`Can't transform [${s}]`) +// }, +// false, +// true +// ) +// ) +// +// const expression = new CucumberExpression( +// 'I have a {throwing} parameter', +// parameterTypeRegistry +// ) +// const args = expression.match('I have a bad parameter') +// assertThrows(() => args[0].getValue(null), "Can't transform [bad]") +// }) +// +// describe('conflicting parameter type', () => { +// it('is detected for type name', () => { +// assertThrows( +// () => +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'color', +// /.*/, +// CssColor, +// s => new CssColor(s), +// false, +// true +// ) +// ), +// 'There is already a parameter type with name color' +// ) +// }) +// +// it('is not detected for type', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'whatever', +// /.*/, +// Color, +// s => new Color(s), +// false, +// true +// ) +// ) +// }) +// +// it('is not detected for regexp', () => { +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'css-color', +// /red|blue|yellow/, +// CssColor, +// s => new CssColor(s), +// true, +// false +// ) +// ) +// +// assert.equal( +// new CucumberExpression( +// 'I have a {css-color} ball', +// parameterTypeRegistry +// ) +// .match('I have a blue ball')[0] +// .getValue(null).constructor, +// CssColor +// ) +// assert.equal( +// new CucumberExpression( +// 'I have a {css-color} ball', +// parameterTypeRegistry +// ) +// .match('I have a blue ball')[0] +// .getValue(null).name, +// 'blue' +// ) +// assert.equal( +// new CucumberExpression('I have a {color} ball', parameterTypeRegistry) +// .match('I have a blue ball')[0] +// .getValue(null).constructor, +// Color +// ) +// assert.equal( +// new CucumberExpression('I have a {color} ball', parameterTypeRegistry) +// .match('I have a blue ball')[0] +// .getValue(null).name, +// 'blue' +// ) +// }) +// }) +// +// // JavaScript-specific +// it('creates arguments using async transform', async () => { +// parameterTypeRegistry = new ParameterTypeRegistry() +// /// [add-async-parameter-type] +// parameterTypeRegistry.defineParameterType( +// new ParameterType( +// 'asyncColor', +// /red|blue|yellow/, +// Color, +// async s => new Color(s), +// false, +// true +// ) +// ) +// /// [add-async-parameter-type] +// +// const expression = new CucumberExpression( +// 'I have a {asyncColor} ball', +// parameterTypeRegistry +// ) +// const args = await expression.match('I have a red ball') +// const value = await args[0].getValue(null) +// assert.equal(value.name, 'red') +// }) +// }) +// +// describe('RegularExpression', () => { +// it('matches arguments with custom parameter type', () => { +// const expression = new RegularExpression( +// /I have a (red|blue|yellow) ball/, +// parameterTypeRegistry +// ) +// const value = expression.match('I have a red ball')[0].getValue(null) +// assert.equal(value.constructor, Color) +// assert.equal(value.name, 'red') +// }) +// }) +// }) From 004874eaa886fd2900d0c97efb68fb900ffb7100 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 28 Mar 2018 16:47:21 -0700 Subject: [PATCH 19/26] update --- .../go/custom_parameter_type_test.go | 277 ++++++++---------- 1 file changed, 118 insertions(+), 159 deletions(-) diff --git a/cucumber-expressions/go/custom_parameter_type_test.go b/cucumber-expressions/go/custom_parameter_type_test.go index ab7ed55eec..412418dd3a 100644 --- a/cucumber-expressions/go/custom_parameter_type_test.go +++ b/cucumber-expressions/go/custom_parameter_type_test.go @@ -1,6 +1,7 @@ package cucumberexpressions_test import ( + "fmt" "regexp" "strconv" "testing" @@ -79,170 +80,128 @@ func TestCustomParameterTypes(t *testing.T) { ) require.NoError(t, err) err = parameterTypeRegistry.DefineParameterType(coordinateParameterType) + require.NoError(t, err) expression, err := cucumberexpressions.NewCucumberExpression("A {int} thick line from {coordinate} to {coordinate}", parameterTypeRegistry) + require.NoError(t, err) args := expression.Match("A 5 thick line from 10,20,30 to 40,50,60") require.Equal(t, 5, args[0].GetValue()) require.Equal(t, &Coordinate{x: 10, y: 20, z: 30}, args[1].GetValue()) require.Equal(t, &Coordinate{x: 40, y: 50, z: 60}, args[2].GetValue()) }) + + t.Run("matches parameters with custom parameter type using optional capture group", func(t *testing.T) { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + colorParameterType, err := cucumberexpressions.NewParameterType( + "color", + []*regexp.Regexp{ + regexp.MustCompile("red|blue|yellow"), + regexp.MustCompile("(?:dark|light) (?:red|blue|yellow)"), + }, + "color", + func(args ...string) interface{} { return &Color{name: args[0]} }, + false, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) + require.NoError(t, err) + args := expression.Match("I have a dark red ball") + require.Equal(t, &Color{name: "dark red"}, args[0].GetValue()) + }) + + t.Run("defers transformation until queried from argument", func(t *testing.T) { + parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() + colorParameterType, err := cucumberexpressions.NewParameterType( + "throwing", + []*regexp.Regexp{regexp.MustCompile("bad")}, + "throwing", + func(args ...string) interface{} { panic(fmt.Sprintf("Can't transform [%s]", args[0])) }, + false, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + require.NoError(t, err) + expression, err := cucumberexpressions.NewCucumberExpression("I have a {throwing} parameter", parameterTypeRegistry) + require.NoError(t, err) + args := expression.Match("I have a bad parameter") + require.NotNil(t, args) + require.PanicsWithValue(t, "Can't transform [bad]", func() { + args[0].GetValue() + }) + }) + + t.Run("conflicting parameter type", func(t *testing.T) { + t.Run("is detected for type name", func(t *testing.T) { + parameterTypeRegistry := CreateParameterTypeRegistry(t) + colorParameterType, err := cucumberexpressions.NewParameterType( + "color", + []*regexp.Regexp{regexp.MustCompile(".*")}, + "CSSColor", + func(args ...string) interface{} { return &CSSColor{name: args[0]} }, + false, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + require.Error(t, err) + require.Equal(t, "There is already a parameter type with name color", err.Error()) + }) + + t.Run("is not detected for type", func(t *testing.T) { + parameterTypeRegistry := CreateParameterTypeRegistry(t) + colorParameterType, err := cucumberexpressions.NewParameterType( + "whatever", + []*regexp.Regexp{regexp.MustCompile(".*")}, + "Color", + func(args ...string) interface{} { return &Color{name: args[0]} }, + false, + true, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + require.NoError(t, err) + }) + + t.Run("is not detected for regexp", func(t *testing.T) { + parameterTypeRegistry := CreateParameterTypeRegistry(t) + colorParameterType, err := cucumberexpressions.NewParameterType( + "css-color", + []*regexp.Regexp{regexp.MustCompile("red|blue|yellow")}, + "CSSColor", + func(args ...string) interface{} { return &CSSColor{name: args[0]} }, + true, + false, + ) + require.NoError(t, err) + err = parameterTypeRegistry.DefineParameterType(colorParameterType) + require.NoError(t, err) + + cssColorExpression, err := cucumberexpressions.NewCucumberExpression("I have a {css-color} ball", parameterTypeRegistry) + require.NoError(t, err) + cssColorArgs := cssColorExpression.Match("I have a blue ball") + require.NotNil(t, cssColorArgs) + require.Equal(t, &CSSColor{name: "blue"}, cssColorArgs[0].GetValue()) + + colorExpression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) + require.NoError(t, err) + colorArgs := colorExpression.Match("I have a blue ball") + require.NotNil(t, colorArgs) + require.Equal(t, &Color{name: "blue"}, colorArgs[0].GetValue()) + }) + }) + + t.Run("RegularExpression", func(t *testing.T) { + t.Run("matches arguments with custom parameter type", func(t *testing.T) { + parameterTypeRegistry := CreateParameterTypeRegistry(t) + expression := cucumberexpressions.NewRegularExpression(regexp.MustCompile("I have a (red|blue|yellow) ball"), parameterTypeRegistry) + args, err := expression.Match("I have a red ball") + require.NoError(t, err) + require.NotNil(t, args) + require.Equal(t, &Color{name: "red"}, args[0].GetValue()) + }) + }) }) } - -// -// describe('CucumberExpression', () => { -// it('matches parameters with custom parameter type using optional capture group', () => { -// parameterTypeRegistry = new ParameterTypeRegistry() -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'color', -// [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/], -// Color, -// s => new Color(s), -// false, -// true -// ) -// ) -// const expression = new CucumberExpression( -// 'I have a {color} ball', -// parameterTypeRegistry -// ) -// const value = expression.match('I have a dark red ball')[0].getValue(null) -// assert.equal(value.name, 'dark red') -// }) -// -// it('defers transformation until queried from argument', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'throwing', -// /bad/, -// null, -// s => { -// throw new Error(`Can't transform [${s}]`) -// }, -// false, -// true -// ) -// ) -// -// const expression = new CucumberExpression( -// 'I have a {throwing} parameter', -// parameterTypeRegistry -// ) -// const args = expression.match('I have a bad parameter') -// assertThrows(() => args[0].getValue(null), "Can't transform [bad]") -// }) -// -// describe('conflicting parameter type', () => { -// it('is detected for type name', () => { -// assertThrows( -// () => -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'color', -// /.*/, -// CssColor, -// s => new CssColor(s), -// false, -// true -// ) -// ), -// 'There is already a parameter type with name color' -// ) -// }) -// -// it('is not detected for type', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'whatever', -// /.*/, -// Color, -// s => new Color(s), -// false, -// true -// ) -// ) -// }) -// -// it('is not detected for regexp', () => { -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'css-color', -// /red|blue|yellow/, -// CssColor, -// s => new CssColor(s), -// true, -// false -// ) -// ) -// -// assert.equal( -// new CucumberExpression( -// 'I have a {css-color} ball', -// parameterTypeRegistry -// ) -// .match('I have a blue ball')[0] -// .getValue(null).constructor, -// CssColor -// ) -// assert.equal( -// new CucumberExpression( -// 'I have a {css-color} ball', -// parameterTypeRegistry -// ) -// .match('I have a blue ball')[0] -// .getValue(null).name, -// 'blue' -// ) -// assert.equal( -// new CucumberExpression('I have a {color} ball', parameterTypeRegistry) -// .match('I have a blue ball')[0] -// .getValue(null).constructor, -// Color -// ) -// assert.equal( -// new CucumberExpression('I have a {color} ball', parameterTypeRegistry) -// .match('I have a blue ball')[0] -// .getValue(null).name, -// 'blue' -// ) -// }) -// }) -// -// // JavaScript-specific -// it('creates arguments using async transform', async () => { -// parameterTypeRegistry = new ParameterTypeRegistry() -// /// [add-async-parameter-type] -// parameterTypeRegistry.defineParameterType( -// new ParameterType( -// 'asyncColor', -// /red|blue|yellow/, -// Color, -// async s => new Color(s), -// false, -// true -// ) -// ) -// /// [add-async-parameter-type] -// -// const expression = new CucumberExpression( -// 'I have a {asyncColor} ball', -// parameterTypeRegistry -// ) -// const args = await expression.match('I have a red ball') -// const value = await args[0].getValue(null) -// assert.equal(value.name, 'red') -// }) -// }) -// -// describe('RegularExpression', () => { -// it('matches arguments with custom parameter type', () => { -// const expression = new RegularExpression( -// /I have a (red|blue|yellow) ball/, -// parameterTypeRegistry -// ) -// const value = expression.match('I have a red ball')[0].getValue(null) -// assert.equal(value.constructor, Color) -// assert.equal(value.name, 'red') -// }) -// }) -// }) From c7b838e0f8252fc771cd8caab1a8928d94ec413a Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 28 Mar 2018 16:48:41 -0700 Subject: [PATCH 20/26] update --- .../go/cucumber_expression.go | 71 ----------- .../go/cucumber_expression_generator.go | 117 ------------------ cucumber-expressions/go/errors.go | 57 --------- 3 files changed, 245 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 98c4d48bf6..acfb0a962a 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -83,74 +83,3 @@ func buildCaptureRegexp(regexps []*regexp.Regexp) string { return fmt.Sprintf("(%s)", strings.Join(captureGroups, "|")) } - -// const Argument = require('./argument') -// const TreeRegexp = require('./tree_regexp') -// const { UndefinedParameterTypeError } = require('./errors') -// -// class CucumberExpression { -// /** -// * @param expression -// * @param parameterTypeRegistry -// */ -// constructor(expression, parameterTypeRegistry) { -// // Does not include (){} characters because they have special meaning -// const ESCAPE_REGEXP = /([\\^[$.|?*+])/g -// const PARAMETER_REGEXP = /{([^}]+)}/g -// const OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/g -// const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^/]+)((\/[^\s^/]+)+)/g -// -// this._expression = expression -// this._parameterTypes = [] -// let regexp = '^' -// let match -// let matchOffset = 0 -// -// // Does not include (){} because they have special meaning -// -// expression = expression.replace(ESCAPE_REGEXP, '\\$1') -// -// // Create non-capturing, optional capture groups from parenthesis -// expression = expression.replace( -// OPTIONAL_REGEXP, -// (match, p1, p2) => (p1 === '\\\\' ? `\\(${p2}\\)` : `(?:${p2})?`) -// ) -// -// expression = expression.replace( -// ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, -// (_, p1, p2) => `(?:${p1}${p2.replace(/\//g, '|')})` -// ) -// -// while ((match = PARAMETER_REGEXP.exec(expression)) !== null) { -// const typeName = match[1] -// -// const parameterType = parameterTypeRegistry.lookupByTypeName(typeName) -// if (!parameterType) throw new UndefinedParameterTypeError(typeName) -// this._parameterTypes.push(parameterType) -// -// const text = expression.slice(matchOffset, match.index) -// const captureRegexp = buildCaptureRegexp(parameterType.regexps) -// matchOffset = PARAMETER_REGEXP.lastIndex -// regexp += text -// regexp += captureRegexp -// } -// regexp += expression.slice(matchOffset) -// regexp += '$' -// this._treeRegexp = new TreeRegexp(regexp) -// } -// -// } -// -// function buildCaptureRegexp(regexps) { -// if (regexps.length === 1) { -// return `(${regexps[0]})` -// } -// -// const captureGroups = regexps.map(group => { -// return `(?:${group})` -// }) -// -// return `(${captureGroups.join('|')})` -// } -// -// module.exports = CucumberExpression diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go index 30d1a97922..39f5fb0d7a 100644 --- a/cucumber-expressions/go/cucumber_expression_generator.go +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -97,120 +97,3 @@ func (c *CucumberExpressionGenerator) createParameterTypeMatchers2(parameterType func escapeForUtilFormat(s string) string { return strings.Replace(s, "%", "%%", -1) } - -// const util = require('util') -// const ParameterTypeMatcher = require('./parameter_type_matcher') -// const ParameterType = require('./parameter_type') -// const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory') -// -// class CucumberExpressionGenerator { -// constructor(parameterTypeRegistry) { -// this._parameterTypeRegistry = parameterTypeRegistry -// } -// -// generateExpressions(text) { -// const parameterTypeCombinations = [] -// const parameterTypeMatchers = this._createParameterTypeMatchers(text) -// let expressionTemplate = '' -// let pos = 0 -// -// // eslint-disable-next-line no-constant-condition -// while (true) { -// let matchingParameterTypeMatchers = [] -// -// for (const parameterTypeMatcher of parameterTypeMatchers) { -// const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos) -// if (advancedParameterTypeMatcher.find) { -// matchingParameterTypeMatchers.push(advancedParameterTypeMatcher) -// } -// } -// -// if (matchingParameterTypeMatchers.length > 0) { -// matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort( -// ParameterTypeMatcher.compare -// ) -// -// // Find all the best parameter type matchers, they are all candidates. -// const bestParameterTypeMatcher = matchingParameterTypeMatchers[0] -// const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter( -// m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0 -// ) -// -// // Build a list of parameter types without duplicates. The reason there -// // might be duplicates is that some parameter types have more than one regexp, -// // which means multiple ParameterTypeMatcher objects will have a reference to the -// // same ParameterType. -// // We're sorting the list so preferential parameter types are listed first. -// // Users are most likely to want these, so they should be listed at the top. -// let parameterTypes = [] -// for (const parameterTypeMatcher of bestParameterTypeMatchers) { -// if ( -// parameterTypes.indexOf(parameterTypeMatcher.parameterType) === -1 -// ) { -// parameterTypes.push(parameterTypeMatcher.parameterType) -// } -// } -// parameterTypes = parameterTypes.sort(ParameterType.compare) -// -// parameterTypeCombinations.push(parameterTypes) -// -// expressionTemplate += escapeForUtilFormat( -// text.slice(pos, bestParameterTypeMatcher.start) -// ) -// expressionTemplate += '{%s}' -// -// pos = -// bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length -// } else { -// break -// } -// -// if (pos >= text.length) { -// break -// } -// } -// -// expressionTemplate += escapeForUtilFormat(text.slice(pos)) -// return new CombinatorialGeneratedExpressionFactory( -// expressionTemplate, -// parameterTypeCombinations -// ).generateExpressions() -// } -// -// /** -// * @deprecated -// */ -// generateExpression(text) { -// return util.deprecate( -// () => this.generateExpressions(text)[0], -// 'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead' -// )() -// } -// -// _createParameterTypeMatchers(text) { -// let parameterMatchers = [] -// for (const parameterType of this._parameterTypeRegistry.parameterTypes) { -// if (parameterType.useForSnippets) { -// parameterMatchers = parameterMatchers.concat( -// this._createParameterTypeMatchers2(parameterType, text) -// ) -// } -// } -// return parameterMatchers -// } -// -// _createParameterTypeMatchers2(parameterType, text) { -// // TODO: [].map -// const result = [] -// for (const regexp of parameterType.regexps) { -// result.push(new ParameterTypeMatcher(parameterType, regexp, text)) -// } -// return result -// } -// } -// -// function escapeForUtilFormat(s) { -// return s.replace(/%/g, '%%') -// } -// -// module.exports = CucumberExpressionGenerator diff --git a/cucumber-expressions/go/errors.go b/cucumber-expressions/go/errors.go index 28a38147a3..754a444a6a 100644 --- a/cucumber-expressions/go/errors.go +++ b/cucumber-expressions/go/errors.go @@ -67,60 +67,3 @@ func NewUndefinedParameterTypeError(typeName string) error { func (e *UndefinedParameterTypeError) Error() string { return e.s } - -// -// class CucumberExpressionError extends Error {} -// -// class AmbiguousParameterTypeError extends CucumberExpressionError { -// static forConstructor( -// keyName, -// keyValue, -// parameterTypes, -// generatedExpressions -// ) { -// return new this( -// `parameter type with ${keyName}=${keyValue} is used by several parameter types: ${parameterTypes}, ${generatedExpressions}` -// ) -// } -// -// static forRegExp( -// parameterTypeRegexp, -// expressionRegexp, -// parameterTypes, -// generatedExpressions -// ) { -// return new this( -// `Your Regular Expression ${expressionRegexp} -// matches multiple parameter types with regexp ${parameterTypeRegexp}: -// ${this._parameterTypeNames(parameterTypes)} -// -// I couldn't decide which one to use. You have two options: -// -// 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these: -// ${this._expressions(generatedExpressions)} -// -// 2) Make one of the parameter types preferential and continue to use a Regular Expression. -// ` -// ) -// } -// -// static _parameterTypeNames(parameterTypes) { -// return parameterTypes.map(p => `{${p.name}}`).join('\n ') -// } -// -// static _expressions(generatedExpressions) { -// return generatedExpressions.map(e => e.source).join('\n ') -// } -// } -// -// class UndefinedParameterTypeError extends CucumberExpressionError { -// constructor(typeName) { -// super(`Undefined parameter type {${typeName}}`) -// } -// } -// -// module.exports = { -// AmbiguousParameterTypeError, -// UndefinedParameterTypeError, -// CucumberExpressionError, -// } From e1138b6b85f3fda8bb187f092ce5d793143f269e Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Thu, 29 Mar 2018 11:26:01 -0700 Subject: [PATCH 21/26] update --- Untitled | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Untitled diff --git a/Untitled b/Untitled deleted file mode 100644 index e69de29bb2..0000000000 From 54ef222895c5534c5cbb6ffdd18b11e0dae295c1 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Thu, 29 Mar 2018 11:37:43 -0700 Subject: [PATCH 22/26] update --- cucumber-expressions/go/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cucumber-expressions/go/Makefile b/cucumber-expressions/go/Makefile index f8b919656c..94dc4cd045 100644 --- a/cucumber-expressions/go/Makefile +++ b/cucumber-expressions/go/Makefile @@ -4,6 +4,9 @@ export GOPATH = $(realpath ./lib) default: test .PHONY: default +# Use env variable ARGS to pass arguments to 'go test' +# (for running only a specific test or using verbose mode) +# Example: ARGS='-v -run TestCucumberExpression' make test test: lib/src/github.com/stretchr/testify go test ${ARGS} .PHONY: clean From 17d1aaa7e15fb30adbc09091d5cfc41af0d4ffc4 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 4 Apr 2018 10:28:55 -0700 Subject: [PATCH 23/26] update --- .../go/cucumber_expression.go | 4 +-- .../go/cucumber_expression_test.go | 25 +++++++++++++------ .../go/custom_parameter_type_test.go | 20 +++++++++------ cucumber-expressions/go/expression.go | 11 ++++++++ .../go/expression_examples_test.go | 13 +++++----- 5 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 cucumber-expressions/go/expression.go diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index acfb0a962a..04dfaa0061 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -59,8 +59,8 @@ func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTy return result, nil } -func (c *CucumberExpression) Match(text string) []*Argument { - return BuildArguments(c.treeRegexp, text, c.parameterTypes) +func (c *CucumberExpression) Match(text string) ([]*Argument, error) { + return BuildArguments(c.treeRegexp, text, c.parameterTypes), nil } func (c *CucumberExpression) Regexp() *regexp.Regexp { diff --git a/cucumber-expressions/go/cucumber_expression_test.go b/cucumber-expressions/go/cucumber_expression_test.go index 0fdce45f51..c6f42ac243 100644 --- a/cucumber-expressions/go/cucumber_expression_test.go +++ b/cucumber-expressions/go/cucumber_expression_test.go @@ -16,7 +16,8 @@ func TestCucumberExpression(t *testing.T) { expr := "I have {int} cuke(s)" expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) - args := expression.Match("I have 7 cukes") + args, err := expression.Match("I have 7 cukes") + require.NoError(t, err) require.Equal(t, args[0].GetValue(), 7) /// [capture-match-arguments] }) @@ -163,8 +164,12 @@ func TestCucumberExpression(t *testing.T) { parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) - require.Nil(t, expression.Match("I have 800 cukes and 3")) - require.NotNil(t, expression.Match("I have 800 cukes and .")) + args, err := expression.Match("I have 800 cukes and 3") + require.NoError(t, err) + require.Nil(t, args) + args, err = expression.Match("I have 800 cukes and .") + require.NoError(t, err) + require.NotNil(t, args) }) t.Run("escapes |", func(t *testing.T) { @@ -172,9 +177,15 @@ func TestCucumberExpression(t *testing.T) { parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) - require.Nil(t, expression.Match("I have 800 cukes and a")) - require.Nil(t, expression.Match("I have 800 cukes and b")) - require.NotNil(t, expression.Match("I have 800 cukes and a|b")) + args, err := expression.Match("I have 800 cukes and a") + require.NoError(t, err) + require.Nil(t, args) + args, err = expression.Match("I have 800 cukes and b") + require.NoError(t, err) + require.Nil(t, args) + args, err = expression.Match("I have 800 cukes and a|b") + require.NoError(t, err) + require.NotNil(t, args) }) }) } @@ -183,7 +194,7 @@ func MatchCucumberExpression(t *testing.T, expr string, text string) []interface parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() expression, err := cucumberexpressions.NewCucumberExpression(expr, parameterTypeRegistry) require.NoError(t, err) - args := expression.Match(text) + args, err := expression.Match(text) require.NoError(t, err) if args == nil { return nil diff --git a/cucumber-expressions/go/custom_parameter_type_test.go b/cucumber-expressions/go/custom_parameter_type_test.go index 412418dd3a..2c47a9442f 100644 --- a/cucumber-expressions/go/custom_parameter_type_test.go +++ b/cucumber-expressions/go/custom_parameter_type_test.go @@ -45,8 +45,9 @@ func TestCustomParameterTypes(t *testing.T) { parameterTypeRegistry := CreateParameterTypeRegistry(t) expression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) require.NoError(t, err) - value := expression.Match("I have a red ball")[0].GetValue() - require.Equal(t, &Color{name: "red"}, value) + args, err := expression.Match("I have a red ball") + require.NoError(t, err) + require.Equal(t, &Color{name: "red"}, args[0].GetValue()) }) t.Run("matches parameters with multiple capture groups", func(t *testing.T) { @@ -83,7 +84,8 @@ func TestCustomParameterTypes(t *testing.T) { require.NoError(t, err) expression, err := cucumberexpressions.NewCucumberExpression("A {int} thick line from {coordinate} to {coordinate}", parameterTypeRegistry) require.NoError(t, err) - args := expression.Match("A 5 thick line from 10,20,30 to 40,50,60") + args, err := expression.Match("A 5 thick line from 10,20,30 to 40,50,60") + require.NoError(t, err) require.Equal(t, 5, args[0].GetValue()) require.Equal(t, &Coordinate{x: 10, y: 20, z: 30}, args[1].GetValue()) require.Equal(t, &Coordinate{x: 40, y: 50, z: 60}, args[2].GetValue()) @@ -107,7 +109,8 @@ func TestCustomParameterTypes(t *testing.T) { require.NoError(t, err) expression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) require.NoError(t, err) - args := expression.Match("I have a dark red ball") + args, err := expression.Match("I have a dark red ball") + require.NoError(t, err) require.Equal(t, &Color{name: "dark red"}, args[0].GetValue()) }) @@ -126,7 +129,8 @@ func TestCustomParameterTypes(t *testing.T) { require.NoError(t, err) expression, err := cucumberexpressions.NewCucumberExpression("I have a {throwing} parameter", parameterTypeRegistry) require.NoError(t, err) - args := expression.Match("I have a bad parameter") + args, err := expression.Match("I have a bad parameter") + require.NoError(t, err) require.NotNil(t, args) require.PanicsWithValue(t, "Can't transform [bad]", func() { args[0].GetValue() @@ -181,13 +185,15 @@ func TestCustomParameterTypes(t *testing.T) { cssColorExpression, err := cucumberexpressions.NewCucumberExpression("I have a {css-color} ball", parameterTypeRegistry) require.NoError(t, err) - cssColorArgs := cssColorExpression.Match("I have a blue ball") + cssColorArgs, err := cssColorExpression.Match("I have a blue ball") + require.NoError(t, err) require.NotNil(t, cssColorArgs) require.Equal(t, &CSSColor{name: "blue"}, cssColorArgs[0].GetValue()) colorExpression, err := cucumberexpressions.NewCucumberExpression("I have a {color} ball", parameterTypeRegistry) require.NoError(t, err) - colorArgs := colorExpression.Match("I have a blue ball") + colorArgs, err := colorExpression.Match("I have a blue ball") + require.NoError(t, err) require.NotNil(t, colorArgs) require.Equal(t, &Color{name: "blue"}, colorArgs[0].GetValue()) }) diff --git a/cucumber-expressions/go/expression.go b/cucumber-expressions/go/expression.go new file mode 100644 index 0000000000..47dc499806 --- /dev/null +++ b/cucumber-expressions/go/expression.go @@ -0,0 +1,11 @@ +package cucumberexpressions + +import ( + "regexp" +) + +type Expression interface { + Match(text string) ([]*Argument, error) + Regexp() *regexp.Regexp + Source() string +} diff --git a/cucumber-expressions/go/expression_examples_test.go b/cucumber-expressions/go/expression_examples_test.go index 789595ee8d..c0340aea7f 100644 --- a/cucumber-expressions/go/expression_examples_test.go +++ b/cucumber-expressions/go/expression_examples_test.go @@ -31,18 +31,17 @@ func TestExamples(t *testing.T) { func MatchExample(t *testing.T, expressionText, text string) []interface{} { parameterTypeRegistry := cucumberexpressions.NewParameterTypeRegistry() - var args []*cucumberexpressions.Argument - var err error + var expression cucumberexpressions.Expression if strings.HasPrefix(expressionText, "/") { r := regexp.MustCompile(expressionText[1 : len(expressionText)-1]) - expression := cucumberexpressions.NewRegularExpression(r, parameterTypeRegistry) - args, err = expression.Match(text) - require.NoError(t, err) + expression = cucumberexpressions.NewRegularExpression(r, parameterTypeRegistry) } else { - expression, err := cucumberexpressions.NewCucumberExpression(expressionText, parameterTypeRegistry) + var err error + expression, err = cucumberexpressions.NewCucumberExpression(expressionText, parameterTypeRegistry) require.NoError(t, err) - args = expression.Match(text) } + args, err := expression.Match(text) + require.NoError(t, err) if args == nil { return nil } From 87fa9871514394164038388186b3be7c423e256d Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 4 Apr 2018 13:11:56 -0700 Subject: [PATCH 24/26] update --- cucumber-expressions/go/argument.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cucumber-expressions/go/argument.go b/cucumber-expressions/go/argument.go index 2601b2f142..1b4159acdd 100644 --- a/cucumber-expressions/go/argument.go +++ b/cucumber-expressions/go/argument.go @@ -37,3 +37,7 @@ func (a *Argument) Group() *Group { func (a *Argument) GetValue() interface{} { return a.parameterType.Transform(a.group.Values()) } + +func (a *Argument) ParameterType() *ParameterType { + return a.parameterType +} From 870d16738aa45c742cb6009c7157bdd244ce8398 Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 4 Apr 2018 16:53:45 -0700 Subject: [PATCH 25/26] address #346 --- .../go/cucumber_expression.go | 97 +++++++++++-------- .../go/cucumber_expression_generator.go | 10 +- .../go/cucumber_expression_generator_test.go | 8 ++ 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/cucumber-expressions/go/cucumber_expression.go b/cucumber-expressions/go/cucumber_expression.go index 04dfaa0061..c839d9ca17 100644 --- a/cucumber-expressions/go/cucumber_expression.go +++ b/cucumber-expressions/go/cucumber_expression.go @@ -6,56 +6,31 @@ import ( "strings" ) +var ESCAPE_REGEXP = regexp.MustCompile(`([\\^[$.|?*+])`) +var PARAMETER_REGEXP = regexp.MustCompile(`(\\\\\\\\)?{([^}]+)}`) +var OPTIONAL_REGEXP = regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) +var ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`) +var DOUBLE_ESCAPE = `\\\\` + type CucumberExpression struct { - expression string + source string parameterTypes []*ParameterType treeRegexp *TreeRegexp } func NewCucumberExpression(expression string, parameterTypeRegistry *ParameterTypeRegistry) (*CucumberExpression, error) { - ESCAPE_REGEXP := regexp.MustCompile(`([\\^[$.|?*+])`) - PARAMETER_REGEXP := regexp.MustCompile("{([^}]+)}") - OPTIONAL_REGEXP := regexp.MustCompile(`(\\\\\\\\)?\([^)]+\)`) - ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP := regexp.MustCompile(`([^\s^/]+)((\/[^\s^/]+)+)`) - - result := &CucumberExpression{expression: expression} - parameterTypes := []*ParameterType{} - r := "^" - matchOffset := 0 - - // Does not include (){} because they have special meaning - expression = ESCAPE_REGEXP.ReplaceAllString(expression, "\\$1") - - // Create non-capturing, optional capture groups from parenthesis - expression = OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { - if strings.HasPrefix(match, "\\\\\\\\") { - return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1]) - } - return fmt.Sprintf("(?:%s)?", match[1:len(match)-1]) - }) - - expression = ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { - return fmt.Sprintf("(?:%s)", strings.Replace(match, "/", "|", -1)) - }) + result := &CucumberExpression{source: expression} - matches := PARAMETER_REGEXP.FindAllStringSubmatchIndex(expression[matchOffset:], -1) - for _, indicies := range matches { - typeName := expression[indicies[2]:indicies[3]] - parameterType := parameterTypeRegistry.LookupByTypeName(typeName) - if parameterType == nil { - return nil, NewUndefinedParameterTypeError(typeName) - } - parameterTypes = append(parameterTypes, parameterType) - text := expression[matchOffset:indicies[0]] - captureRegexp := buildCaptureRegexp(parameterType.regexps) - matchOffset = indicies[1] - r += text - r += captureRegexp + expression = result.processEscapes(expression) + expression = result.processOptional(expression) + expression = result.processAlteration(expression) + expression, err := result.processParameters(expression, parameterTypeRegistry) + if err != nil { + return nil, err } + expression = "^" + expression + "$" - r += expression[matchOffset:] + "$" - result.parameterTypes = parameterTypes - result.treeRegexp = NewTreeRegexp(regexp.MustCompile(r)) + result.treeRegexp = NewTreeRegexp(regexp.MustCompile(expression)) return result, nil } @@ -68,7 +43,45 @@ func (c *CucumberExpression) Regexp() *regexp.Regexp { } func (c *CucumberExpression) Source() string { - return c.expression + return c.source +} + +func (c *CucumberExpression) processEscapes(expression string) string { + return ESCAPE_REGEXP.ReplaceAllString(expression, `\$1`) +} + +func (c *CucumberExpression) processOptional(expression string) string { + return OPTIONAL_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + if strings.HasPrefix(match, DOUBLE_ESCAPE) { + return fmt.Sprintf(`\(%s\)`, match[5:len(match)-1]) + } + return fmt.Sprintf("(?:%s)?", match[1:len(match)-1]) + }) +} + +func (c *CucumberExpression) processAlteration(expression string) string { + return ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + return fmt.Sprintf("(?:%s)", strings.Replace(match, "/", "|", -1)) + }) +} + +func (c *CucumberExpression) processParameters(expression string, parameterTypeRegistry *ParameterTypeRegistry) (string, error) { + var err error + result := PARAMETER_REGEXP.ReplaceAllStringFunc(expression, func(match string) string { + if strings.HasPrefix(match, DOUBLE_ESCAPE) { + return fmt.Sprintf(`\{%s\}`, match[5:len(match)-1]) + } + + typeName := match[1 : len(match)-1] + parameterType := parameterTypeRegistry.LookupByTypeName(typeName) + if parameterType == nil { + err = NewUndefinedParameterTypeError(typeName) + return match + } + c.parameterTypes = append(c.parameterTypes, parameterType) + return buildCaptureRegexp(parameterType.regexps) + }) + return result, err } func buildCaptureRegexp(regexps []*regexp.Regexp) string { diff --git a/cucumber-expressions/go/cucumber_expression_generator.go b/cucumber-expressions/go/cucumber_expression_generator.go index 39f5fb0d7a..3e27cb399f 100644 --- a/cucumber-expressions/go/cucumber_expression_generator.go +++ b/cucumber-expressions/go/cucumber_expression_generator.go @@ -62,7 +62,7 @@ func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*Genera }) parameterTypeCombinations = append(parameterTypeCombinations, parameterTypes) - expressionTemplate += escapeForUtilFormat(text[pos:bestParameterTypeMatcher.Start()]) + "{%s}" + expressionTemplate += escape(text[pos:bestParameterTypeMatcher.Start()]) + "{%s}" pos = bestParameterTypeMatcher.Start() + len(bestParameterTypeMatcher.Group()) } else { break @@ -72,7 +72,7 @@ func (c *CucumberExpressionGenerator) GenerateExpressions(text string) []*Genera break } } - expressionTemplate += escapeForUtilFormat(text[pos:]) + expressionTemplate += escape(text[pos:]) return NewCombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).GenerateExpressions() } @@ -94,6 +94,8 @@ func (c *CucumberExpressionGenerator) createParameterTypeMatchers2(parameterType return result } -func escapeForUtilFormat(s string) string { - return strings.Replace(s, "%", "%%", -1) +func escape(s string) string { + result := strings.Replace(s, "%", "%%", -1) + result = strings.Replace(result, `(`, `\(`, -1) + return strings.Replace(result, `{`, `\{`, -1) } diff --git a/cucumber-expressions/go/cucumber_expression_generator_test.go b/cucumber-expressions/go/cucumber_expression_generator_test.go index ba385c7a73..5572ce166c 100644 --- a/cucumber-expressions/go/cucumber_expression_generator_test.go +++ b/cucumber-expressions/go/cucumber_expression_generator_test.go @@ -30,6 +30,14 @@ func TestCucumberExpressionGeneratory(t *testing.T) { assertExpression(t, "hello", []string{}, "hello") }) + t.Run("generates expression with escaped left parenthesis", func(t *testing.T) { + assertExpression(t, `\(iii)`, []string{}, "(iii)") + }) + + t.Run("generates expression with escaped left curly brace", func(t *testing.T) { + assertExpression(t, `\{iii}`, []string{}, "{iii}") + }) + t.Run("generates expression for int float arg", func(t *testing.T) { assertExpression( t, From f25d0e25c01c0db14d62ebd2ea0850897b90b6aa Mon Sep 17 00:00:00 2001 From: Charles Rudolph Date: Wed, 4 Apr 2018 21:06:15 -0700 Subject: [PATCH 26/26] remove comment --- cucumber-expressions/go/parameter_type.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber-expressions/go/parameter_type.go b/cucumber-expressions/go/parameter_type.go index 24798caa86..a4932a85f6 100644 --- a/cucumber-expressions/go/parameter_type.go +++ b/cucumber-expressions/go/parameter_type.go @@ -27,7 +27,6 @@ func NewParameterType(name string, regexps []*regexp.Regexp, type1 string, trans return nil, errors.New("ParameterType Regexps can't use flags") } } - // TODO error if uses flags return &ParameterType{ name: name, regexps: regexps,