UNPKG

@cucumber/gherkin

Version:
332 lines (305 loc) 8.75 kB
import { type DataTable, type DocString, type GherkinDocument, type IdGenerator, type Pickle, type PickleDocString, type PickleStep, type PickleStepArgument, PickleStepType, type PickleTable, type PickleTag, type Rule, type Scenario, type Step, StepKeywordType, type TableCell, type TableRow, type Tag, } from '@cucumber/messages' const pickleStepTypeFromKeyword: { [key in StepKeywordType]: PickleStepType } = { [StepKeywordType.UNKNOWN]: PickleStepType.UNKNOWN, [StepKeywordType.CONTEXT]: PickleStepType.CONTEXT, [StepKeywordType.ACTION]: PickleStepType.ACTION, [StepKeywordType.OUTCOME]: PickleStepType.OUTCOME, [StepKeywordType.CONJUNCTION]: null, } export default function compile( gherkinDocument: GherkinDocument, uri: string, newId: IdGenerator.NewId ): readonly Pickle[] { const pickles: Pickle[] = [] if (gherkinDocument.feature == null) { return pickles } const feature = gherkinDocument.feature const language = feature.language const featureTags = feature.tags let featureBackgroundSteps: Step[] = [] feature.children.forEach((stepsContainer) => { if (stepsContainer.background) { featureBackgroundSteps = [].concat(stepsContainer.background.steps) } else if (stepsContainer.rule) { compileRule( featureTags, featureBackgroundSteps, stepsContainer.rule, language, pickles, uri, newId ) } else if (stepsContainer.scenario.examples.length === 0) { compileScenario( featureTags, featureBackgroundSteps, stepsContainer.scenario, language, pickles, uri, newId ) } else { compileScenarioOutline( featureTags, featureBackgroundSteps, stepsContainer.scenario, language, pickles, uri, newId ) } }) return pickles } function compileRule( featureTags: readonly Tag[], featureBackgroundSteps: readonly Step[], rule: Rule, language: string, pickles: Pickle[], uri: string, newId: IdGenerator.NewId ) { let ruleBackgroundSteps = [].concat(featureBackgroundSteps) const tags = [].concat(featureTags).concat(rule.tags) rule.children.forEach((stepsContainer) => { if (stepsContainer.background) { ruleBackgroundSteps = ruleBackgroundSteps.concat(stepsContainer.background.steps) } else if (stepsContainer.scenario.examples.length === 0) { compileScenario( tags, ruleBackgroundSteps, stepsContainer.scenario, language, pickles, uri, newId ) } else { compileScenarioOutline( tags, ruleBackgroundSteps, stepsContainer.scenario, language, pickles, uri, newId ) } }) } function compileScenario( inheritedTags: readonly Tag[], backgroundSteps: readonly Step[], scenario: Scenario, language: string, pickles: Pickle[], uri: string, newId: IdGenerator.NewId ) { let lastKeywordType = StepKeywordType.UNKNOWN const steps = [] as PickleStep[] if (scenario.steps.length !== 0) { backgroundSteps.forEach((step) => { lastKeywordType = step.keywordType === StepKeywordType.CONJUNCTION ? lastKeywordType : step.keywordType steps.push(pickleStep(step, [], null, newId, lastKeywordType)) }) } const tags = [].concat(inheritedTags).concat(scenario.tags) scenario.steps.forEach((step) => { lastKeywordType = step.keywordType === StepKeywordType.CONJUNCTION ? lastKeywordType : step.keywordType steps.push(pickleStep(step, [], null, newId, lastKeywordType)) }) const pickle: Pickle = { id: newId(), uri, location: scenario.location, astNodeIds: [scenario.id], tags: pickleTags(tags), name: scenario.name, language, steps, } pickles.push(pickle) } function compileScenarioOutline( inheritedTags: readonly Tag[], backgroundSteps: readonly Step[], scenario: Scenario, language: string, pickles: Pickle[], uri: string, newId: IdGenerator.NewId ) { scenario.examples .filter((e) => e.tableHeader) .forEach((examples) => { const variableCells = examples.tableHeader.cells examples.tableBody.forEach((valuesRow) => { let lastKeywordType = StepKeywordType.UNKNOWN const steps = [] as PickleStep[] if (scenario.steps.length !== 0) { backgroundSteps.forEach((step) => { lastKeywordType = step.keywordType === StepKeywordType.CONJUNCTION ? lastKeywordType : step.keywordType steps.push(pickleStep(step, [], null, newId, lastKeywordType)) }) } scenario.steps.forEach((scenarioOutlineStep) => { lastKeywordType = scenarioOutlineStep.keywordType === StepKeywordType.CONJUNCTION ? lastKeywordType : scenarioOutlineStep.keywordType const step = pickleStep( scenarioOutlineStep, variableCells, valuesRow, newId, lastKeywordType ) steps.push(step) }) const id = newId() const tags = pickleTags( [].concat(inheritedTags).concat(scenario.tags).concat(examples.tags) ) pickles.push({ id, uri, location: valuesRow.location, astNodeIds: [scenario.id, valuesRow.id], name: interpolate(scenario.name, variableCells, valuesRow.cells), language, steps, tags, }) }) }) } function pickleDocString( argumentIndex: number, argument: DocString, variableCells: readonly TableCell[], valueCells: readonly TableCell[] ): PickleDocString { return { argumentIndex, content: interpolate(argument.content, variableCells, valueCells), mediaType: argument.mediaType ? interpolate(argument.mediaType, variableCells, valueCells) : undefined, } } function pickleTable( argumentIndex: number, argument: DataTable, variableCells: readonly TableCell[], valueCells: readonly TableCell[] ): PickleTable { return { argumentIndex, rows: argument.rows.map((row) => { return { cells: row.cells.map((cell) => { return { value: interpolate(cell.value, variableCells, valueCells), } }), } }), } } function createPickleArguments( step: Step, variableCells: readonly TableCell[], valueCells: readonly TableCell[] ): PickleStepArgument | undefined { if (step.dataTable && step.docString) { const tableFirst = step.docString.location.line > step.dataTable.location.line return { docString: pickleDocString(tableFirst ? 2 : 1, step.docString, variableCells, valueCells), dataTable: pickleTable(tableFirst ? 1 : 2, step.dataTable, variableCells, valueCells), } } else if (step.dataTable) { return { dataTable: pickleTable(undefined, step.dataTable, variableCells, valueCells), } } else if (step.docString) { return { docString: pickleDocString(undefined, step.docString, variableCells, valueCells), } } return undefined } function interpolate( name: string, variableCells: readonly TableCell[], valueCells: readonly TableCell[] ) { variableCells.forEach((variableCell, n) => { const valueCell = valueCells[n] const valuePattern = `<${variableCell.value}>` const escapedPattern = valuePattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') const regexp = new RegExp(escapedPattern, 'g') // JS Specific - dollar sign needs to be escaped with another dollar sign // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter const replacement = valueCell.value.replace(/\$/g, '$$$$') name = name.replace(regexp, replacement) }) return name } function pickleStep( step: Step, variableCells: readonly TableCell[], valuesRow: TableRow | null, newId: IdGenerator.NewId, keywordType: StepKeywordType ): PickleStep { const astNodeIds = [step.id] if (valuesRow) { astNodeIds.push(valuesRow.id) } const valueCells = valuesRow ? valuesRow.cells : [] return { id: newId(), text: interpolate(step.text, variableCells, valueCells), type: pickleStepTypeFromKeyword[keywordType], argument: createPickleArguments(step, variableCells, valueCells), astNodeIds: astNodeIds, } } function pickleTags(tags: Tag[]): readonly PickleTag[] { return tags.map(pickleTag) } function pickleTag(tag: Tag): PickleTag { return { name: tag.name, astNodeId: tag.id, } }