@cucumber/gherkin-utils
Version:
Various Gherkin utilities
166 lines (151 loc) • 5 kB
text/typescript
import * as messages from '@cucumber/messages'
import { GherkinDocumentHandlers } from './GherkinDocumentHandlers'
/**
* Walks a Gherkin Document, visiting each node depth first (in the order they appear in the source)
*
* @param gherkinDocument
* @param initialValue the initial value of the traversal
* @param handlers handlers for each node type, which may return a new value
* @return result the final value
*/
export function walkGherkinDocument<Acc>(
gherkinDocument: messages.GherkinDocument,
initialValue: Acc,
handlers: Partial<GherkinDocumentHandlers<Acc>>
): Acc {
const commentsStack = gherkinDocument.comments.slice()
let acc = initialValue
const h: GherkinDocumentHandlers<Acc> = { ...makeDefaultHandlers<Acc>(), ...handlers }
const feature = gherkinDocument.feature
acc = walkComments(popCommentsUntil(feature?.location), acc)
if (!feature) return acc
acc = walkTags(feature.tags || [], acc)
acc = h.feature(feature, acc)
for (const child of feature.children) {
if (child.background) {
acc = walkStepContainer(child.background, acc)
} else if (child.scenario) {
acc = walkStepContainer(child.scenario, acc)
} else if (child.rule) {
acc = walkTags(child.rule.tags || [], acc)
acc = walkComments(popCommentsUntil(child.rule.location), acc)
acc = h.rule(child.rule, acc)
for (const ruleChild of child.rule.children) {
if (ruleChild.background) {
acc = walkStepContainer(ruleChild.background, acc)
} else if (ruleChild.scenario) {
acc = walkStepContainer(ruleChild.scenario, acc)
}
}
}
}
acc = walkComments(popRemainingContents(), acc)
return acc
function walkComments(comments: readonly messages.Comment[], acc: Acc): Acc {
return comments.reduce((acc, comment) => h.comment(comment, acc), acc)
}
function walkTags(tags: readonly messages.Tag[], acc: Acc): Acc {
return tags.reduce((acc, tag) => h.tag(tag, acc), acc)
}
function walkSteps(steps: readonly messages.Step[], acc: Acc): Acc {
return steps.reduce((acc, step) => walkStep(step, acc), acc)
}
function walkStep(step: messages.Step, acc: Acc): Acc {
acc = walkComments(popCommentsUntil(step.location), acc)
acc = h.step(step, acc)
if (step.docString) {
acc = h.docString(step.docString, acc)
}
if (step.dataTable) {
acc = h.dataTable(step.dataTable, acc)
acc = walkTableRows(step.dataTable.rows, acc)
}
return acc
}
function walkTableRows(tableRows: readonly messages.TableRow[], acc: Acc): Acc {
return tableRows.reduce((acc, tableRow) => walkTableRow(tableRow, acc), acc)
}
function walkTableRow(tableRow: messages.TableRow, acc: Acc): Acc {
acc = h.tableRow(tableRow, acc)
return tableRow.cells.reduce((acc, tableCell) => h.tableCell(tableCell, acc), acc)
}
function walkStepContainer(
stepContainer: messages.Scenario | messages.Background,
acc: Acc
): Acc {
acc = walkComments(popCommentsUntil(stepContainer.location), acc)
const scenario: messages.Scenario = 'tags' in stepContainer ? stepContainer : null
acc = walkTags(scenario?.tags || [], acc)
acc = scenario
? h.scenario(scenario, acc)
: h.background(stepContainer as messages.Background, acc)
acc = walkSteps(stepContainer.steps, acc)
if (scenario) {
for (const examples of scenario.examples || []) {
acc = walkComments(popCommentsUntil(examples.location), acc)
acc = walkTags(examples.tags || [], acc)
acc = h.examples(examples, acc)
if (examples.tableHeader) {
acc = walkTableRow(examples.tableHeader, acc)
acc = walkTableRows(examples.tableBody || [], acc)
}
}
}
return acc
}
function popCommentsUntil(location?: messages.Location): readonly messages.Comment[] {
let count = 0
for (const comment of commentsStack) {
if (location === undefined || comment.location.line < location.line) {
count++
} else {
break
}
}
return commentsStack.splice(0, count)
}
function popRemainingContents(): readonly messages.Comment[] {
return commentsStack.splice(0, commentsStack.length)
}
}
function makeDefaultHandlers<Acc>() {
const defaultHandlers: GherkinDocumentHandlers<Acc> = {
feature(feature, acc) {
return acc
},
background(background, acc) {
return acc
},
rule(rule, acc) {
return acc
},
scenario(scenario, acc) {
return acc
},
step(step, acc) {
return acc
},
examples(examples, acc) {
return acc
},
tag(tag, acc) {
return acc
},
comment(comment, acc) {
return acc
},
dataTable(dataTable, acc) {
return acc
},
tableRow(tableRow, acc) {
return acc
},
tableCell(tableCell, acc) {
return acc
},
docString(docString, acc) {
return acc
},
}
return defaultHandlers
}