UNPKG

prettier-plugin-gherkin

Version:

This prettier plugin format your gherkin (`.feature` files) documents.

319 lines (318 loc) 9.93 kB
export function isWithLocation(node) { return typeof node === 'object' && node !== null && 'location' in node; } function reEscapeTableCell(str) { const out = str // espace all pipes that have been unescaped .replaceAll('|', '\\|') // espace all backslashes that have been unescaped .replace(/\\/g, '\\\\'); if (OPTIONS.escapeBackslashes) { return out; } // replace all escaped backslashes, except those that are followed by a newline or a pipe return out.replace(/(\\\\)(?![n\n\\])/g, '\\'); } /** * Set the max size of each column in the table */ function generateColumnSizes(tableRows) { return tableRows.reduce((acc, row) => { row.cells.forEach((cell, index) => { if (!acc[index]) { acc[index] = 0; } acc[index] = Math.max(acc[index], reEscapeTableCell(cell.value).length); }); return acc; }, []); } export class TypedGherkinNode { constructor(originalNode) { } } export function isHasChildren(node) { return typeof node === 'object' && node !== null && 'children' in node; } export function isHasChild(node) { return typeof node === 'object' && node !== null && 'child' in node; } export class TypedGherkinNodeWithLocation extends TypedGherkinNode { location; constructor(originalNode) { super(originalNode); this.location = originalNode.location; } get lastLine() { return this.location.line; } } /** * An options object that will be set for a document * It is stored as a singleton that will be reset on each document to avoid passing it around for now. * A better option may to have access to the options on every classes. */ let OPTIONS; export class TypedGherkinDocument extends TypedGherkinNode { uri; feature; comments; constructor(originalNode, options) { super(originalNode); OPTIONS = options; this.uri = originalNode.uri; this.feature = originalNode.feature ? new TypedFeature(originalNode.feature) : undefined; this.comments = originalNode.comments.map((c) => new TypedComment(c)); } get child() { return this.feature; } } export class TypedFeature extends TypedGherkinNodeWithLocation { tags; language; keyword; name; description; children; constructor(originalNode) { super(originalNode); this.tags = originalNode.tags.map((t) => new TypedTag(t)); this.language = originalNode.language; this.keyword = originalNode.keyword; this.name = originalNode.name; this.description = originalNode.description; this.children = originalNode.children.map((c) => new TypedFeatureChild(c)); } } export class TypedTag extends TypedGherkinNodeWithLocation { name; id; constructor(originalNode) { super(originalNode); this.name = originalNode.name; this.id = originalNode.id; } } export class TypedComment extends TypedGherkinNodeWithLocation { text; constructor(originalNode) { super(originalNode); this.text = originalNode.text; } get value() { return this.text.trim(); } } export class TypedFeatureChild extends TypedGherkinNode { rule; background; scenario; constructor(originalNode) { super(originalNode); this.rule = originalNode.rule ? new TypedRule(originalNode.rule) : undefined; this.background = originalNode.background ? new TypedBackground(originalNode.background) : undefined; this.scenario = originalNode.scenario ? new TypedScenario(originalNode.scenario) : undefined; } get child() { return this.rule || this.background || this.scenario; } } export class TypedRule extends TypedGherkinNodeWithLocation { tags; keyword; name; description; children; id; constructor(originalNode) { super(originalNode); this.tags = originalNode.tags.map((t) => new TypedTag(t)); this.keyword = originalNode.keyword; this.name = originalNode.name; this.description = originalNode.description; this.children = originalNode.children.map((c) => new TypedRuleChild(c)); this.id = originalNode.id; } } export class TypedRuleChild extends TypedGherkinNode { background; scenario; constructor(originalNode) { super(originalNode); this.background = originalNode.background ? new TypedBackground(originalNode.background) : undefined; this.scenario = originalNode.scenario ? new TypedScenario(originalNode.scenario) : undefined; } get child() { return this.scenario; } } export class TypedBackground extends TypedGherkinNodeWithLocation { keyword; name; description; steps; id; constructor(originalNode) { super(originalNode); this.keyword = originalNode.keyword; this.name = originalNode.name; this.description = originalNode.description; this.steps = originalNode.steps.map((s) => new TypedStep(s)); this.id = originalNode.id; } get children() { return this.steps; } } export class TypedStep extends TypedGherkinNodeWithLocation { keyword; keywordType; text; docString; dataTable; id; constructor(originalNode) { super(originalNode); this.keyword = originalNode.keyword; this.keywordType = originalNode.keywordType; this.text = originalNode.text; this.docString = originalNode.docString ? new TypedDocString(originalNode.docString) : undefined; this.dataTable = originalNode.dataTable ? new TypedDataTable(originalNode.dataTable) : undefined; this.id = originalNode.id; } get children() { return [this.docString, this.dataTable].filter((c) => typeof c !== 'undefined'); } get lastLine() { const dataTableLength = this.dataTable?.nbRows ?? 0; const docStringLength = this.docString?.nbRows ?? 0; return this.location.line + dataTableLength + docStringLength; } } export class TypedScenario extends TypedGherkinNodeWithLocation { tags; keyword; name; description; steps; examples; id; constructor(originalNode) { super(originalNode); this.location = originalNode.location; this.tags = originalNode.tags.map((t) => new TypedTag(t)); this.keyword = originalNode.keyword; this.name = originalNode.name; this.description = originalNode.description; this.steps = originalNode.steps.map((s) => new TypedStep(s)); this.examples = originalNode.examples.map((e) => new TypedExamples(e)); this.id = originalNode.id; } get children() { return [...this.steps, ...this.examples, ...this.tags]; } } export class TypedExamples extends TypedGherkinNodeWithLocation { tags; keyword; name; description; tableHeader; tableBody; id; constructor(originalNode) { super(originalNode); const rows = [ originalNode.tableHeader, ...(originalNode.tableBody ?? []), ].filter((row) => typeof row !== 'undefined'); const columnSizes = generateColumnSizes(rows); this.tags = originalNode.tags.map((t) => new TypedTag(t)); this.keyword = originalNode.keyword; this.name = originalNode.name; this.description = originalNode.description; this.tableHeader = originalNode.tableHeader ? new TypedTableRow(originalNode.tableHeader, columnSizes) : undefined; this.tableBody = originalNode.tableBody.map((r) => new TypedTableRow(r, columnSizes)); this.id = originalNode.id; } get children() { return [ ...(this.tableHeader ? [this.tableHeader] : []), ...this.tableBody, ...this.tags, ]; } } export class TypedTableRow extends TypedGherkinNodeWithLocation { cells; id; columnSizes; constructor(originalNode, columnSizes) { super(originalNode); this.cells = originalNode.cells.map((c, index) => new TypedTableCell(c, columnSizes[index])); this.id = originalNode.id; this.columnSizes = columnSizes; } get children() { return this.cells; } } export class TypedTableCell extends TypedGherkinNodeWithLocation { value; displaySize; constructor(originalNode, displaySize) { super(originalNode); this.value = reEscapeTableCell(originalNode.value); this.displaySize = Math.max(displaySize ?? 0, this.value.length); } } export class TypedDocString extends TypedGherkinNodeWithLocation { mediaType; content; delimiter; constructor(originalNode) { super(originalNode); this.mediaType = originalNode.mediaType; this.content = originalNode.content; this.delimiter = originalNode.delimiter; } get nbRows() { return this.content.split('\n').length + 2; } } export class TypedDataTable extends TypedGherkinNodeWithLocation { rows; constructor(originalNode) { super(originalNode); const columnSizes = generateColumnSizes(originalNode.rows); this.rows = originalNode.rows.map((r) => new TypedTableRow(r, columnSizes)); } get nbRows() { return (this.rows.reduce((acc, row) => { // @ts-expect-error comments are added by prettier directly const commentLines = row.comments?.length ?? 0; return acc + commentLines + 1; }, 0) ?? 0); } get children() { return this.rows; } }