gherkin-precompiler
Version:
Simple pre-compiler for Gherkin feature files
156 lines (143 loc) • 5.2 kB
JavaScript
;
const DefaultConfig = require('../DefaultConfig');
const {Tag} = require('gherkin-ast');
const ObjectSet = require('object-set-type');
/**
* @typedef {Object} RemoveDuplicatesConfiguration
* @property {boolean} processTags
* @property {boolean} processRows
* @property {boolean} verbose
*/
const DEFAULT_CONFIG = {
processTags: true,
processRows: false,
verbose: true
};
const removeDuplicates = array => Array.from(new Set(array));
/**
* Precompiler which removes duplicate tags and/or data table rows.
* @class
* @extends DefaultConfig
*/
class RemoveDuplicates extends DefaultConfig {
/**
* @constructor
* @param {RemoveDuplicatesConfiguration} config
*/
constructor(config) {
super();
/** @member {RemoveDuplicatesConfiguration} */
this.config = Object.assign({}, DEFAULT_CONFIG, config || {});
/** @member {Function} */
this.logTag = this._getLogger(this.config.processTags);
/** @member {Function} */
this.logRow = this._getLogger(this.config.processRows);
}
/**
* Creates an appropriate logger, based on configuration.
* @private
* @param {boolean} enabled
* @returns {Function}
*/
_getLogger(enabled) {
return this.config.verbose ? (...args) => console[enabled ? 'log' : 'warn'].apply(console, args) : () => null;
}
/**
* Checkes whether the given object has a tag with given name.
* @private
* @param {Scenario|ScenarioOutline|Examples|Feature} element
* @param {string} tagName
* @returns {boolean}
*/
_hasTag(element, tagName) {
if (!element.tags || !element.tags.length) {
return false;
}
return element.tags.some(tag => tag.name === tagName);
}
/**
* Removes duplicated tags from the given object.
* It removes:
* - tags which exists on parent too
* - duplicate tags
* @private
* @param {Scenario|ScenarioOutline|Examples|Feature} element
* @param {Feature} [parent]
*/
_filterTags(element, parent) {
if (element.tags && element.tags.length) {
const ownTags = element.tags.filter(tag => {
if (this._hasTag(parent, tag.name)) {
this.logTag(`The ${tag.name} presents on feature too on "${element.keyword} ${element.name}" in "${parent.keyword} ${parent.name}"`);
return false;
}
return true;
});
const tagNames = ownTags.map(tag => tag.name);
const uniqueTagNames = removeDuplicates(tagNames);
if (tagNames.length !== uniqueTagNames) {
uniqueTagNames.filter(tag => {
return tagNames.reduce((n, name) => n + (name === tag ? 1 : 0), 0) > 1;
}).forEach(tag => {
this.logTag(`The ${tag} presents multiple times on "${element.keyword} ${element.name}" in "${parent.keyword} ${parent.name}"`);
});
}
if (this.config.processTags) {
element.tags = uniqueTagNames.map(tag => new Tag(tag));
}
}
}
/**
* Removes duplicate rows from the body of the examples.
* @private
* @param {Examples} examples
* @param {ScenarioOutline} scenario
* @param {Feature} parent
*/
_filterRows(examples, scenario, parent) {
if (examples.body && examples.body.length) {
const rowSet = new ObjectSet();
let prevSize = 0;
examples.body.forEach(row => {
rowSet.add(row);
if (rowSet.size === prevSize) {
this.logRow(`The [${row.cells.map(cell => cell.value).join('|')}] presents multiple times on "${scenario.keyword} ${scenario.name}" in "${parent.keyword} ${parent.name}"`);
}
prevSize = rowSet.size;
});
if (this.config.processRows && rowSet.size !== examples.body.length) {
examples.body = Array.from(rowSet);
}
}
}
/**
* Event handler for feature event.
* @param {Feature} feature
*/
onFeature(feature) {
this._filterTags(feature);
}
/**
* Event handler for scenario event.
* @param {Scenario} scenario
* @param {Feature} parent
*/
onScenario(scenario, parent) {
this._filterTags(scenario, parent);
}
/**
* Event handler for scenarioOutline event.
* @param {ScenarioOutline} scenarioOutline
* @param {Feature} parent
*/
onScenarioOutline(scenarioOutline, parent) {
this._filterTags(scenarioOutline, parent);
if (scenarioOutline.examples) {
scenarioOutline.examples.forEach(examples => {
this._filterTags(examples, parent);
this._filterRows(examples, scenarioOutline, parent);
});
}
}
}
module.exports = RemoveDuplicates;