diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bc8bcbc..5f41380cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) on how to contribute to Cucumber. ## [Unreleased] +### Fixed +- Correctly escape backslashes in generated expressions for snippets ([#1324](https://github.com/cucumber/cucumber-js/issues/1324) [#1995](https://github.com/cucumber/cucumber-js/pull/1995)) ## [8.0.0] - 2022-04-06 ### Changed diff --git a/features/step_definition_snippets.feature b/features/step_definition_snippets.feature index 7163441b9..beab9d03d 100644 --- a/features/step_definition_snippets.feature +++ b/features/step_definition_snippets.feature @@ -70,3 +70,20 @@ Feature: step definition snippets return 'pending'; }); """ + + Scenario: a step resulting in special characters in the expression + Given a file named "features/number.feature" with: + """ + Feature: a feature + Scenario: a scenario + Given a person's (secret) desires + """ + When I run cucumber-js + Then it fails + And the output contains the text: + """ + Given('a person\'s \\(secret) desires', function () { + // Write code here that turns the phrase above into concrete actions + return 'pending'; + }); + """ diff --git a/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax.ts b/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax.ts index 128904e96..09dd4b949 100644 --- a/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax.ts +++ b/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax.ts @@ -1,3 +1,4 @@ +import { GeneratedExpression } from '@cucumber/cucumber-expressions' import { ISnippetSnytax, ISnippetSyntaxBuildOptions, @@ -43,9 +44,8 @@ export default class JavaScriptSnippetSyntax implements ISnippetSnytax { if (this.snippetInterface === SnippetInterface.Callback) { allParameterNames.push(CALLBACK_NAME) } - return `${prefix + functionName}('${generatedExpression.source.replace( - /'/g, - "\\'" + return `${prefix + functionName}('${this.escapeSpecialCharacters( + generatedExpression )}', ${functionKeyword}(${allParameterNames.join(', ')}) {\n` } ) @@ -56,4 +56,13 @@ export default class JavaScriptSnippetSyntax implements ISnippetSnytax { '});' ) } + + private escapeSpecialCharacters(generatedExpression: GeneratedExpression) { + let source = generatedExpression.source + // double up any backslashes because we're in a javascript string + source = source.replace(/\\/g, '\\\\') + // escape any single quotes because that's our quote delimiter + source = source.replace(/'/g, "\\'") + return source + } } diff --git a/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax_spec.ts b/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax_spec.ts index 7d76edb94..dc6434f31 100644 --- a/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax_spec.ts +++ b/src/formatter/step_definition_snippet_builder/javascript_snippet_syntax_spec.ts @@ -145,6 +145,33 @@ describe('JavascriptSnippetSyntax', () => { }) }) + describe('pattern contains escapes', () => { + it('returns the proper snippet', () => { + // Arrange + const syntax = new JavascriptSnippetSyntax(SnippetInterface.Synchronous) + const buildOptions: ISnippetSyntaxBuildOptions = { + comment: 'comment', + functionName: 'functionName', + generatedExpressions: generateExpressions( + 'the user (with permissions) executes the action' + ), + stepParameterNames: [], + } + + // Act + const result = syntax.build(buildOptions) + + // Assert + expect(result).to.eql( + reindent(` + functionName('the user \\\\(with permissions) executes the action', function () { + // comment + return 'pending'; + });`) + ) + }) + }) + describe('multiple patterns', () => { it('returns the snippet with the other choices commented out', function () { // Arrange