Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-cucumber

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-cucumber - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

.travis.yml

77

docs/rules/async-then.md

@@ -1,8 +0,8 @@

# ensures Then steps are asynchronous (async-then)
# Ensures step/hook implementations are asynchronous (async-then)
When using an async heavy tool like protractor you want to ensure that your steps wait for async actions to complete before completing the scenario otherwise you can run through all the steps without ever checking anything.
When using an async heavy tool like protractor you want to ensure that your steps and/or hooks wait for async actions to complete before completing the scenario otherwise you can run through all the steps without ever checking anything.
## Rule Details
This rule aims to force you to either return something (presumably a promise) or provide a callback function in your Then steps.
This rule aims to force you to either return something (presumably a promise) or provide a callback function in your steps and/or hooks (by default, just `Then`s) - or use an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function).

@@ -13,3 +13,3 @@ The following patterns are considered warnings:

this.Then(/some step/, function () {
Then(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');

@@ -24,18 +24,79 @@ });

this.Then(/some step/, function () {
Then(/some step/, function () {
return expect(foo).to.eventually.eql('bar');
});
this.Then(/some step/, function (done) {
Then(/some step/, function (done) {
expect(foo).to.eventually.eql('bar').notify(done);
});
this.When(/some step/, function () {
Then(/some step/, async function () {
// anything, since async functions implicitly return a promise
});
When(/some step/, function () {
// do anything you want in Whens, we'll wait in the Thens
}
});
```
## Options
By default, only calls to `Then` are checked.
The rule has an object option where you can override this:
- `"all"` - a boolean, if `true` then all supported step/hook words will be checked (`Given`, `When`, `Then`, `Before`, `BeforeAll`, `After`, `AfterAll`)
- `"words"`- an array of strings, denoting which step/hook words to check
Examples of additional **incorrect** code for this rule with a sample `{ "all": true }` option:
```js
/*eslint cucumber/async-then: ["error", { "all": true }] */
Given(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
When(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
Before(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
BeforeAll(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
After(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
AfterAll(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
```
Examples of additional **incorrect** code for this rule with a sample `{ "words": [ "Given", "When", "Then" ] }` option:
```js
/*eslint cucumber/async-then: ["error", { "words": [ "Given", "When", "Then" ] }] */
Given(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
When(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
Then(/some step/, function () {
expect(foo).to.eventually.eql('doesn\'t matter because it will never be checked');
});
```
## When Not To Use It
When you're not dealing with asynchronous operations

@@ -6,2 +6,3 @@ const _ = require('lodash');

'async-then': require('./rules/async-then'),
'expression-type': require('./rules/expression-type'),
'no-restricted-tags': require('./rules/no-restricted-tags'),

@@ -8,0 +9,0 @@ 'no-arrow-functions': require('./rules/no-arrow-functions')

157

lib/rules/async-then.js

@@ -1,63 +0,122 @@

module.exports = function(context) {
const CALLBACK_NAMES = /^(next|done|callback)$/;
const CALLBACK_NAMES = /^(next|done|callback)$/;
const ALL_WORDS = [
'Given',
'When',
'Then',
'Before',
'BeforeAll',
'After',
'AfterAll'
];
function isThenStep(node) {
return isCucumberOneThenStep(node) || isCucumberTwoPlusThenStep(node);
}
function getMatchedStepOrHook(node, targetWords) {
return (
getCucumberOneMatchedStepOrHook(node, targetWords) ||
getCucumberTwoPlusMatchedStepOrHook(node, targetWords)
);
}
function isCucumberOneThenStep(node) {
return (
node.callee &&
node.callee.object &&
node.callee.object.type === 'ThisExpression' &&
node.callee.property &&
node.callee.property.name === 'Then'
);
function getCucumberOneMatchedStepOrHook(node, targetWords) {
if (
node.callee &&
node.callee.object &&
node.callee.object.type === 'ThisExpression' &&
node.callee.property &&
targetWords.indexOf(node.callee.property.name) !== -1
) {
return node.callee.property.name;
} else {
return false;
}
}
function isCucumberTwoPlusThenStep(node) {
return (
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'Then'
);
function getCucumberTwoPlusMatchedStepOrHook(node, targetWords) {
if (
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
targetWords.indexOf(node.callee.name) !== -1
) {
return node.callee.name;
} else {
return false;
}
}
function didNotReturnAnythingIn(func) {
const statements = func.body.body;
return (
!statements.length ||
statements[statements.length - 1].type !== 'ReturnStatement'
);
}
function didNotReturnAnythingIn(func) {
const statements = func.body.body;
return (
!statements.length ||
statements[statements.length - 1].type !== 'ReturnStatement'
);
}
function doesNotHaveCallback(func) {
return (
!func.params.length ||
!CALLBACK_NAMES.exec(func.params[func.params.length - 1].name)
);
}
function doesNotHaveCallback(func) {
return (
!func.params.length ||
!CALLBACK_NAMES.exec(func.params[func.params.length - 1].name)
);
}
function isAsyncFunction(func) {
return !!func.async;
function isAsyncFunction(func) {
return !!func.async;
}
function getTargetWords(context) {
if (context.options.length) {
const optionsObj = context.options[0];
if (optionsObj.all) {
return ALL_WORDS;
}
if (optionsObj.words) {
return optionsObj.words;
}
}
return ['Then'];
}
return {
CallExpression: function(node) {
if (isThenStep(node)) {
const stepBody = node.arguments[node.arguments.length - 1];
module.exports = {
meta: {
schema: [
{
type: 'object',
properties: {
all: {
type: 'boolean'
},
words: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
}
}
]
},
if (isAsyncFunction(stepBody)) {
return;
create: function(context) {
const targetWords = getTargetWords(context);
return {
CallExpression: function(node) {
const matchedStepOrHook = getMatchedStepOrHook(node, targetWords);
if (matchedStepOrHook) {
const stepBody = node.arguments[node.arguments.length - 1];
if (isAsyncFunction(stepBody)) {
return;
}
if (
doesNotHaveCallback(stepBody) &&
didNotReturnAnythingIn(stepBody)
) {
context.report(
node.callee.property || node.callee,
`${matchedStepOrHook} implementation should be async; use an async function, return a promise, or have a callback`
);
}
}
if (doesNotHaveCallback(stepBody) && didNotReturnAnythingIn(stepBody)) {
context.report(
node.callee.property || node.callee,
"Then step didn't return a promise or have a callback."
);
}
}
}
};
};
}
};
{
"name": "eslint-plugin-cucumber",
"version": "1.3.0",
"version": "1.4.0",
"description": "eslint rules for cucumber steps",

@@ -29,3 +29,4 @@ "keywords": [

"format": "prettier --loglevel warn --write \"**/*.js\"",
"test": "npm run --silent format && script/test.sh"
"test": "mocha tests/**/*.js",
"verify": "npm run format && npm run test"
},

@@ -37,2 +38,3 @@ "dependencies": {

"eslint": "^4.0.0",
"mocha": "^6.2.1",
"prettier": "^1.14.2"

@@ -39,0 +41,0 @@ },

@@ -40,2 +40,3 @@ # eslint-plugin-cucumber

"cucumber/async-then": 2,
"cucumber/expression-type": 2,
"cucumber/no-restricted-tags": [2, "wip", "broken", "foo"],

@@ -51,5 +52,6 @@ "cucumber/no-arrow-functions": 2

| ------------- | ------------- |
| async-then | If you assume asynchronous steps then your Then steps should either be an async function, return a promise or provide a callback function |
| no-restricted-tags | Restrict usage of specified tags |
| no-arrow-functions | Restrict usage of arrow functions on step definitions |
| [async-then](docs/rules/async-then.md) | If you assume asynchronous steps/hooks then your implementation should either be an async function, return a promise or provide a callback function|
| [expression-type](docs/rules/expression-type.md) | Restrict steps to either Cucumber Expressions or Regular Expressions |
| [no-restricted-tags](docs/rules/no-restricted-tags.md) | Restrict usage of specified tags |
| [no-arrow-functions](docs/rules/no-arrow-functions.md) | Restrict usage of arrow functions on step definitions |

@@ -56,0 +58,0 @@ ## For Maintainers

@@ -8,2 +8,3 @@ const rule = require('../../../lib/rules/async-then');

valid: [
// no options provided, just check `Then`s
'this.Then(/step/, async function () {})',

@@ -22,6 +23,54 @@ 'Then(/step/, async function () {})',

'this.When(/step/, function () {})',
'When(/step/, function () {})'
'When(/step/, function () {})',
'this.Before(/step/, function () {})',
'Before(/step/, function () {})',
'this.BeforeAll(/step/, function () {})',
'BeforeAll(/step/, function () {})',
'this.After(/step/, function () {})',
'After(/step/, function () {})',
'this.AfterAll(/step/, function () {})',
'AfterAll(/step/, function () {})',
// words specified in options
{
code: 'Given(/step/, async function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'When(/step/, async function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'Then(/step/, function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'Before(/step/, function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'BeforeAll(/step/, function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'After(/step/, function () {})',
options: [{words: ['Given', 'When']}]
},
{
code: 'AfterAll(/step/, function () {})',
options: [{words: ['Given', 'When']}]
},
// all words option
{code: 'Given(/step/, async function () {})', options: [{all: true}]},
{code: 'When(/step/, async function () {})', options: [{all: true}]},
{code: 'Then(/step/, async function () {})', options: [{all: true}]},
{code: 'Before(/step/, async function () {})', options: [{all: true}]},
{code: 'BeforeAll(/step/, async function () {})', options: [{all: true}]},
{code: 'After(/step/, async function () {})', options: [{all: true}]},
{code: 'AfterAll(/step/, async function () {})', options: [{all: true}]}
],
invalid: [
// no options provided, just check `Then`s
{

@@ -31,3 +80,4 @@ code: 'this.Then(/step/, function () {})',

{
message: "Then step didn't return a promise or have a callback."
message:
'Then implementation should be async; use an async function, return a promise, or have a callback'
}

@@ -40,7 +90,102 @@ ]

{
message: "Then step didn't return a promise or have a callback."
message:
'Then implementation should be async; use an async function, return a promise, or have a callback'
}
]
},
// words specified in options
{
code: 'Given(/step/, function () {})',
errors: [
{
message:
'Given implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{words: ['Given', 'When']}]
},
{
code: 'When(/step/, function () {})',
errors: [
{
message:
'When implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{words: ['Given', 'When']}]
},
// all words option
{
code: 'Given(/step/, function () {})',
errors: [
{
message:
'Given implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'When(/step/, function () {})',
errors: [
{
message:
'When implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'Then(/step/, function () {})',
errors: [
{
message:
'Then implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'Before(/step/, function () {})',
errors: [
{
message:
'Before implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'BeforeAll(/step/, function () {})',
errors: [
{
message:
'BeforeAll implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'After(/step/, function () {})',
errors: [
{
message:
'After implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
},
{
code: 'AfterAll(/step/, function () {})',
errors: [
{
message:
'AfterAll implementation should be async; use an async function, return a promise, or have a callback'
}
],
options: [{all: true}]
}
]
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc