gherkin-precompiler
Version:
Simple pre-compiler for Gherkin feature files
283 lines (259 loc) • 9.83 kB
JavaScript
'use strict';
const METHODS = {
FILTER: {
TAG: {
PRE: 'preFilterTag',
POST: 'postFilterTag'
},
SCENARIO: {
PRE: 'preFilterScenario',
POST: 'postFilterScenario'
},
STEP: {
PRE: 'preFilterStep',
POST: 'postFilterStep'
},
ROW: {
PRE: 'preFilterRow',
POST: 'postFilterRow'
},
EXAMPLES: {
PRE: 'preFilterExamples',
POST: 'postFilterExamples'
}
},
EVENT: {
FEATURE: 'onFeature',
SCENARIO: 'onScenario',
BACKGROUND: 'onBackground',
SCENARIO_OUTLINE: 'onScenarioOutline',
STEP: 'onStep',
TAG: 'onTag',
DOC_STRING: 'onDocString',
DATA_TABLE: 'onDataTable',
EXAMPLES: 'onExamples',
EXAMPLE_HEADER: 'onExampleHeader',
EXAMPLE_ROW: 'onExampleRow'
}
};
/**
* Gherkin feature file pre-compilers.
* @class
*/
class PreCompiler {
/**
* @param {Object|DefaultConfig} config
* @param config
*/
constructor(config) {
this.config = config || {};
}
/**
* Applies the pre-compiler to the given AST.
*
* @param {GherkinDocument} ast
* @returns {GherkinDocument}
*/
applyToAST(ast) {
const result = ast.clone();
this._applyToFeature(result.feature, result);
return result;
}
/**
* Filters the given list with given method.
*
* @param {Array} list
* @param {Object} parent
* @param {string} method
* @returns {Array}
* @private
*/
_filter(list, parent, method) {
if (!this.config[method]) {
return list;
}
return list.filter((item, i) => this.config[method](item, parent, i) !== false);
}
/**
* Applies the given event method on the given element and
* handles various return value possibilities.
*
* @param {Object} parent
* @param {string} key
* @param {string} method
* @private
*/
_handleEvent(parent, key, method) {
if (this.config[method]) {
const result = this.config[method](parent[key], parent);
if (result !== undefined) {
parent[key] = result;
}
}
}
/**
* Applies the given event method on the given list element
* and handles various return value possiblities.
*
* @param {Array} list
* @param {Object} parent
* @param {number} i
* @param {string} method
* @private
*/
_handleListEvent(list, parent, i, method) {
if (this.config[method]) {
const result = this.config[method](list[i], parent, i);
if (result === null) {
list.splice(i, 1);
} else if (Array.isArray(result)) {
list.splice.apply(list, [i, 1].concat(result));
} else if (result !== undefined) {
list[i] = result;
}
}
}
/**
* Applies the process events on Fags.
*
* @param {Array.<Tag>} tags
* @param {Feature|Scenario|ScenarioOutline|Background|Examples} parent
* @private
*/
_applyToTags(tags, parent) {
for (let i = 0; i < tags.length; ++i) {
this._handleListEvent(tags, parent, i, METHODS.EVENT.TAG);
}
}
/**
* Applies the process events to Feature.
*
* @param {Feature} feature
* @param {GherkinDocument} doc
* @private
*/
_applyToFeature(feature, doc) {
this._handleEvent(doc, 'feature', METHODS.EVENT.FEATURE);
feature.tags = this._filter(feature.tags, feature, METHODS.FILTER.TAG.PRE);
this._applyToTags(feature.tags, feature);
feature.tags = this._filter(feature.tags, feature, METHODS.FILTER.TAG.POST);
feature.elements = this._filter(feature.elements, feature, METHODS.FILTER.SCENARIO.PRE);
for (let i = 0; i < feature.elements.length; ++i) {
const element = feature.elements[i];
switch (element.constructor.name) {
case 'Scenario':
this._applyToScenario(element, feature, i);
break;
case 'ScenarioOutline':
this._applyToScenarioOutline(element, feature, i);
break;
case 'Background':
this._applyToBackground(element, feature, i);
break;
}
}
feature.elements = this._filter(feature.elements, feature, METHODS.FILTER.SCENARIO.POST);
}
/**
* Applies the process events to Scenario.
*
* @param {Scenario} scenario
* @param {Feature} feature
* @param {number} i
* @private
*/
_applyToScenario(scenario, feature, i) {
this._handleListEvent(feature.elements, feature, i, 'onScenario');
scenario.tags = this._filter(scenario.tags, scenario, METHODS.FILTER.TAG.PRE);
this._applyToTags(scenario.tags, scenario);
scenario.tags = this._filter(scenario.tags, scenario, METHODS.FILTER.TAG.POST);
scenario.steps = this._filter(scenario.steps, scenario, METHODS.FILTER.STEP.PRE);
for (let i = 0; i < scenario.steps.length; ++i) {
this._applyToStep(scenario.steps[i], scenario, i);
}
scenario.steps = this._filter(scenario.steps, scenario, METHODS.FILTER.STEP.POST);
}
/**
* Applies the process events to ScenarioOutline.
*
* @param {ScenarioOutline} scenarioOutline
* @param {Feature} feature
* @param {number} i
* @private
*/
_applyToScenarioOutline(scenarioOutline, feature, i) {
this._handleListEvent(feature.elements, feature, i, METHODS.EVENT.SCENARIO_OUTLINE);
scenarioOutline.tags = this._filter(scenarioOutline.tags, scenarioOutline, METHODS.FILTER.TAG.PRE);
this._applyToTags(scenarioOutline.tags, scenarioOutline);
scenarioOutline.tags = this._filter(scenarioOutline.tags, scenarioOutline, METHODS.FILTER.TAG.POST);
scenarioOutline.steps = this._filter(scenarioOutline.steps, scenarioOutline, METHODS.FILTER.STEP.PRE);
for (let i = 0; i < scenarioOutline.steps.length; ++i) {
this._applyToStep(scenarioOutline.steps[i], scenarioOutline, i);
}
scenarioOutline.steps = this._filter(scenarioOutline.steps, scenarioOutline, METHODS.FILTER.STEP.POST);
scenarioOutline.examples = this._filter(scenarioOutline.examples, scenarioOutline, METHODS.FILTER.EXAMPLES.PRE);
for (let i = 0; i < scenarioOutline.examples.length; ++i) {
this._applyToExamples(scenarioOutline.examples[i], scenarioOutline, i);
}
scenarioOutline.examples = this._filter(scenarioOutline.examples, scenarioOutline, METHODS.FILTER.EXAMPLES.POST);
}
/**
* Applies the process events to Background.
*
* @param {Background} background
* @param {Feature} feature
* @param {number} i
* @private
*/
_applyToBackground(background, feature, i) {
this._handleListEvent(feature.elements, feature, i, METHODS.EVENT.BACKGROUND);
background.steps = this._filter(background.steps, background, METHODS.FILTER.STEP.PRE);
for (let i = 0; i < background.steps.length; ++i) {
this._applyToStep(background.steps[i], background, i);
}
background.steps = this._filter(background.steps, background, METHODS.FILTER.STEP.POST);
}
/**
* Applies the process events to Step.
*
* @param {Step} step
* @param {Background|Scenario|ScenarioOutline} parent
* @param {number} i
* @private
*/
_applyToStep(step, parent, i) {
this._handleListEvent(parent.steps, parent, i, METHODS.EVENT.STEP);
if (step.argument) {
switch (step.argument.constructor.name) {
case 'DocString':
this._handleEvent(step, 'argument', METHODS.EVENT.DOC_STRING);
break;
case 'DataTable':
step.argument.rows = this._filter(step.argument.rows, step.argument, METHODS.FILTER.ROW.PRE);
this._handleEvent(step, 'argument', METHODS.EVENT.DATA_TABLE);
step.argument.rows = this._filter(step.argument.rows, step.argument, METHODS.FILTER.ROW.POST);
break;
}
}
}
/**
* Applies the process events to Examples.
* @param {Examples} examples
* @param {ScenarioOutline} scenarioOutline
* @param {number} i
* @private
*/
_applyToExamples(examples, scenarioOutline, i) {
this._handleListEvent(scenarioOutline.examples, scenarioOutline, i, METHODS.EVENT.EXAMPLES);
this._handleEvent(examples, 'header', METHODS.EVENT.EXAMPLE_HEADER);
examples.tags = this._filter(examples.tags, examples, METHODS.FILTER.TAG.PRE);
this._applyToTags(examples.tags, examples);
examples.tags = this._filter(examples.tags, examples, METHODS.FILTER.TAG.POST);
examples.body = this._filter(examples.body, examples, METHODS.FILTER.ROW.PRE);
for(let i = 0; i < examples.body.length; ++i) {
this._handleListEvent(examples.body, examples, i, METHODS.EVENT.EXAMPLE_ROW);
}
examples.body = this._filter(examples.body, examples, METHODS.FILTER.ROW.POST);
}
}
module.exports = PreCompiler;