How to use getSingle method of AstNode class

Best Gherkin-php code snippet using AstNode.getSingle

Run Gherkin-php automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

GherkinDocumentBuilder.php

Source: GherkinDocumentBuilder.php Github

copy
1<?php
2
3declare(strict_types=1);
4
5namespace Cucumber\Gherkin;
6
7use Cucumber\Gherkin\Parser\Builder;
8use Cucumber\Gherkin\Parser\RuleType;
9use Cucumber\Gherkin\Parser\TokenType;
10use Cucumber\Gherkin\ParserException\AstBuilderException;
11use Cucumber\Messages\Background;
12use Cucumber\Messages\Comment;
13use Cucumber\Messages\DataTable;
14use Cucumber\Messages\DocString;
15use Cucumber\Messages\Examples;
16use Cucumber\Messages\Feature;
17use Cucumber\Messages\FeatureChild;
18use Cucumber\Messages\GherkinDocument;
19use Cucumber\Messages\Id\IdGenerator;
20use Cucumber\Messages\Location as MessageLocation;
21use Cucumber\Messages\Rule;
22use Cucumber\Messages\RuleChild;
23use Cucumber\Messages\Scenario;
24use Cucumber\Messages\Step;
25use Cucumber\Messages\TableCell;
26use Cucumber\Messages\TableRow;
27use Cucumber\Messages\Tag;
28use LogicException;
29
30/**
31 * @implements Builder<GherkinDocument>
32 */
33final class GherkinDocumentBuilder implements Builder
34{
35    /** @var list<Comment> */
36    private array $comments = [];
37
38    /** @var non-empty-list<AstNode> */
39    private array $stack;
40
41    public function __construct(
42        private readonly string $uri,
43        private readonly IdGenerator $idGenerator,
44    ) {
45        $this->reset($uri);
46    }
47
48    public function build(Token $token): void
49    {
50        if (null === $token->match) {
51            throw new LogicException('Token was not yet matched');
52        }
53
54        $ruleType = RuleType::cast($token->match->tokenType);
55
56        if ($token->match->tokenType == TokenType::Comment) {
57            $this->comments[] = new Comment($this->getLocation($token->match, 0), $token->match->text);
58        } else {
59            $this->currentNode()->add($ruleType, $token->match);
60        }
61    }
62
63    public function startRule(RuleType $ruleType): void
64    {
65        array_push($this->stack, new AstNode($ruleType));
66    }
67
68    public function endRule(RuleType $ruleType): void
69    {
70        $node = array_pop($this->stack);
71        $transformedNode = $this->getTransformedNode($node);
72        if (null !== $transformedNode) {
73            $this->currentNode()->add($node->ruleType, $transformedNode);
74        }
75    }
76
77    public function getResult(): GherkinDocument
78    {
79        $document = $this->currentNode()->getSingle(GherkinDocument::class, Ruletype::GherkinDocument);
80
81        if (null === $document) {
82            throw new LogicException('GherkinDocument was not built from source, but no parse errors');
83        }
84
85        return $document;
86    }
87
88    public function reset(string $uri): void
89    {
90        $this->stack = [new AstNode(RuleType::None)];
91    }
92
93    private function currentNode(): AstNode
94    {
95        return $this->stack[array_key_last($this->stack)];
96    }
97
98    /**
99     * @return object|string|list<object>|null
100     */
101    private function getTransformedNode(AstNode $node): object|string|array|null
102    {
103        return match ($node->ruleType) {
104            RuleType::Step => $this->transformStepNode($node),
105            RuleType::DocString => $this->transformDocStringNode($node),
106            RuleType::ScenarioDefinition => $this->transformScenarioDefinitionNode($node),
107            RuleType::ExamplesDefinition => $this->transformExamplesDefinitionNode($node),
108            RuleType::ExamplesTable => $this->transformExamplesTableNode($node),
109            RuleType::DataTable => $this->transformDataTableNode($node),
110            Ruletype::Background => $this->transformBackgroundNode($node),
111            RuleType::Description => $this->transformDescriptionNode($node),
112            RuleType::Feature => $this->transformFeatureNode($node),
113            RuleType::Rule => $this->transformRuleNode($node),
114            RuleType::GherkinDocument => $this->transformGherkinDocumentNode($node),
115            default => $node,
116        };
117    }
118
119    private function getLocation(TokenMatch $token, int $column): MessageLocation
120    {
121        $column = ($column === 0) ? $token->location->column : $column;
122
123        return new MessageLocation($token->location->line, $column);
124    }
125
126    private function getDescription(AstNode $node): string
127    {
128        return (string) $node->getSingleUntyped(RuleType::Description, "");
129    }
130
131    /** @return list<Step> */
132    private function getSteps(AstNode $node): array
133    {
134        return $node->getitems(Step::class, RuleType::Step);
135    }
136
137    /** @return list<TableRow> */
138    private function getTableRows(AstNode $node): array
139    {
140        $rows = array_map(
141            fn ($token) => new TableRow($this->getLocation($token, 0), $this->getCells($token), $this->idGenerator->newId()),
142            $node->getTokenMatches(TokenType::TableRow),
143        );
144
145        $this->ensureCellCount($rows);
146
147        return $rows;
148    }
149
150    /** @param list<TableRow> $rows */
151    private function ensureCellCount(array $rows): void
152    {
153        if (!count($rows)) {
154            return;
155        }
156
157        $cellCount = count($rows[0]->cells);
158        foreach ($rows as $row) {
159            if (count($row->cells) !== $cellCount) {
160                $location = new Location($row->location->line, $row->location->column ?? 0);
161                throw new AstBuilderException('inconsistent cell count within the table', $location);
162            }
163        }
164    }
165
166    /**
167     * @return list<TableCell>
168     */
169    private function getCells(TokenMatch $token): array
170    {
171        return array_map(
172            fn ($cellItem) => new TableCell($this->getLocation($token, $cellItem->column), $cellItem->text),
173            $token->items,
174        );
175    }
176
177    /**
178     * @return list<Tag>
179     */
180    private function getTags(AstNode $node): array
181    {
182        $tagsNode = $node->getSingle(AstNode::class, RuleType::Tags, new AstNode(RuleType::None));
183
184        $tokens = $tagsNode->getTokenMatches(TokenType::TagLine);
185        $tags = [];
186        foreach ($tokens as $token) {
187            foreach ($token->items as $tagItem) {
188                $tags[] = new Tag(
189                    location: $this->getLocation($token, $tagItem->column),
190                    name: $tagItem->text,
191                    id: $this->idGenerator->newId(),
192                );
193            }
194        }
195
196        return $tags;
197    }
198
199    /**
200     * @param array<TokenMatch> $lineTokens
201     */
202    private function joinMatchedTextWithLinebreaks(array $lineTokens): string
203    {
204        return join("\n", array_map(fn ($t) => $t->text, $lineTokens));
205    }
206
207    private function transformStepNode(AstNode $node): Step
208    {
209        $stepLine = $node->getTokenMatch(TokenType::StepLine);
210
211        return new Step(
212            location: $this->getLocation($stepLine, 0),
213            keyword: $stepLine->keyword,
214            text: $stepLine->text,
215            docString: $node->getSingle(DocString::class, RuleType::DocString),
216            dataTable: $node->getSingle(DataTable::class, RuleType::DataTable),
217            id: $this->idGenerator->newId(),
218        );
219    }
220
221    private function transformDocStringNode(AstNode $node): DocString
222    {
223        $separatorToken = $node->getTokenMatches(TokenType::DocStringSeparator)[0];
224        $mediaType = $separatorToken->text;
225        $lineTokens = $node->getTokenMatches(TokenType::Other);
226
227        $content = $this->joinMatchedTextWithLinebreaks($lineTokens);
228
229        return new DocString(
230            location: $this->getLocation($separatorToken, 0),
231            mediaType: $mediaType ?: null, // special case turns '' into null
232            content: $content,
233            delimiter: $separatorToken->keyword,
234        );
235    }
236
237    private function transformScenarioDefinitionNode(AstNode $node): ?Scenario
238    {
239        $scenarioNode = $node->getSingle(AstNode::class, RuleType::Scenario);
240        if (null === $scenarioNode) {
241            return null;
242        }
243        $scenarioLine = $scenarioNode->getTokenMatch(TokenType::ScenarioLine);
244
245        return new Scenario(
246            location: $this->getLocation($scenarioLine, 0),
247            tags: $this->getTags($node),
248            keyword: $scenarioLine->keyword,
249            name: $scenarioLine->text,
250            description: $this->getDescription($scenarioNode),
251            steps: $this->getSteps($scenarioNode),
252            examples: $scenarioNode->getItems(Examples::class, RuleType::ExamplesDefinition),
253            id: $this->idGenerator->newId(),
254        );
255    }
256
257    private function transformExamplesDefinitionNode(AstNode $node): ?Examples
258    {
259        $examplesNode = $node->getSingle(AstNode::class, RuleType::Examples);
260        if (null === $examplesNode) {
261            return null;
262        }
263        $examplesLine = $examplesNode->getTokenMatch(TokenType::ExamplesLine);
264        /** @var list<TableRow>|null $rows */
265        $rows = $examplesNode->getSingleUntyped(RuleType::ExamplesTable);
266        $tableHeader = is_array($rows) && count($rows) ? $rows[0] : null;
267        $tableBody = (is_array($rows) && count($rows) > 0) ? array_slice($rows, 1) : [];
268
269        return new Examples(
270            location: $this->getLocation($examplesLine, 0),
271            tags: $this->getTags($node),
272            keyword: $examplesLine->keyword,
273            name: $examplesLine->text,
274            description: $this->getDescription($examplesNode),
275            tableHeader: $tableHeader,
276            tableBody: $tableBody,
277            id: $this->idGenerator->newId(),
278        );
279    }
280
281    private function transformDataTableNode(AstNode $node): DataTable
282    {
283        $rows = $this->getTableRows($node);
284
285        return new DataTable($rows[0]->location, $rows);
286    }
287
288    /** @return list<TableRow> */
289    private function transformExamplesTableNode(AstNode $node): array
290    {
291        return $this->getTableRows($node);
292    }
293
294    private function transformBackgroundNode(AstNode $node): Background
295    {
296        $backgroundLine = $node->getTokenMatch(TokenType::BackgroundLine);
297
298        return new Background(
299            location: $this->getLocation($backgroundLine, 0),
300            keyword: $backgroundLine->keyword,
301            name: $backgroundLine->text,
302            description: $this->getDescription($node),
303            steps: $this->getSteps($node),
304            id: $this->idGenerator->newId(),
305        );
306    }
307
308    private function transformDescriptionNode(AstNode $node): string
309    {
310        $lineTokens = $node->getTokenMatches(TokenType::Other);
311
312        $lineText = preg_replace(
313            '/(\\n\\s*)*$/u',
314            '',
315            $this->joinMatchedTextWithLinebreaks($lineTokens),
316        );
317
318        return $lineText;
319    }
320
321    private function transformFeatureNode(AstNode $node): ?Feature
322    {
323        $header = $node->getSingle(AstNode::class, RuleType::FeatureHeader, new AstNode(RuleType::FeatureHeader));
324        if (!$header instanceof AstNode) {
325            return null;
326        }
327        $tags = $this->getTags($header);
328        $featureLine = $header->getTokenMatch(TokenType::FeatureLine);
329
330        $children = [];
331
332        $background = $node->getSingle(Background::class, RuleType::Background);
333        if ($background instanceof Background) {
334            $children[] = new FeatureChild(background: $background);
335        }
336
337        foreach ($node->getItems(Scenario::class, RuleType::ScenarioDefinition) as $scenario) {
338            $children[] = new FeatureChild(scenario: $scenario);
339        }
340
341        foreach ($node->getItems(Rule::class, RuleType::Rule) as $rule) {
342            $children[] = new FeatureChild($rule, null, null);
343        }
344
345        $language = $featureLine->gherkinDialect->getLanguage();
346
347        return new Feature(
348            location: $this->getLocation($featureLine, 0),
349            tags: $tags,
350            language: $language,
351            keyword: $featureLine->keyword,
352            name: $featureLine->text,
353            description: $this->getDescription($header),
354            children: $children,
355        );
356    }
357
358    private function transformRuleNode(AstNode $node): Rule
359    {
360        $header = $node->getSingle(AstNode::class, RuleType::RuleHeader, new AstNode(RuleType::RuleHeader));
361
362        $ruleLine = $header->getTokenMatch(TokenType::RuleLine);
363
364        $children = [];
365        $tags = $this->getTags($header);
366
367        $background = $node->getSingle(Background::class, RuleType::Background);
368        if ($background) {
369            $children[] = new RuleChild(background: $background);
370        }
371        $scenarios = $node->getItems(Scenario::class, RuleType::ScenarioDefinition);
372        foreach ($scenarios as $scenario) {
373            $children[] = new RuleChild(scenario: $scenario);
374        }
375
376        return new Rule(
377            location: $this->getLocation($ruleLine, 0),
378            tags: $tags,
379            keyword: $ruleLine->keyword,
380            name: $ruleLine->text,
381            description: $this->getDescription($header),
382            children: $children,
383            id: $this->idGenerator->newId(),
384        );
385    }
386
387    private function transformGherkinDocumentNode(AstNode $node): GherkinDocument
388    {
389        $feature = $node->getSingle(Feature::class, RuleType::Feature);
390
391        return new GherkinDocument(
392            uri: $this->uri,
393            feature: $feature,
394            comments: $this->comments,
395        );
396    }
397}
398
Full Screen

AstNodeTest.php

Source: AstNodeTest.php Github

copy
1<?php
2
3declare(strict_types=1);
4
5namespace Cucumber\Gherkin;
6
7use Cucumber\Gherkin\Parser\RuleType;
8use Cucumber\Gherkin\Parser\TokenType;
9use PHPUnit\Framework\TestCase;
10use stdClass;
11
12final class AstNodeTest extends TestCase
13{
14    private AstNode $astNode;
15
16    public function setUp(): void
17    {
18        $this->astNode = new AstNode(RuleType::None);
19    }
20
21    public function testItHasARuletype(): void
22    {
23        self::assertSame(RuleType::None, $this->astNode->ruleType);
24    }
25
26    public function testGetItemsReturnsEmptyListIfNotAddedYet(): void
27    {
28        $items = $this->astNode->getItems(stdClass::class, RuleType::None);
29
30        self::assertSame([], $items);
31    }
32
33    public function testGetItemsReturnsAddedItems(): void
34    {
35        $this->astNode->add(RuleType::None, $obj1 = new stdClass());
36        $this->astNode->add(RuleType::None, $obj2 = new stdClass());
37
38        self::assertSame([$obj1, $obj2], $this->astNode->getItems(stdClass::class, RuleType::None));
39    }
40
41    public function testItGetsDefaultResultWhenNoItemsAdded(): void
42    {
43        $item = $this->astNode->getSingle(stdClass::class, RuleType::None, $obj = new stdClass());
44
45        self::assertSame($obj, $item);
46    }
47
48    public function testItGetsFirstSingleItemWhenMultipleAdded(): void
49    {
50        $this->astNode->add(RuleType::None, $obj1 = new stdClass());
51        $this->astNode->add(RuleType::None, $obj2 = new stdClass());
52
53        $item = $this->astNode->getSingle(stdClass::class, RuleType::None, $obj3 = new stdClass());
54
55        self::assertSame($obj1, $item);
56    }
57
58    public function testItGetsNoTokensWhenNoneAreAdded(): void
59    {
60        $tokens = $this->astNode->getTokenMatches(TokenType::Empty);
61
62        self::assertSame([], $tokens);
63    }
64
65    public function testItGetsTokensWhenTheyAreAddedByRuletype(): void
66    {
67        $this->astNode->add(RuleType::_Empty, $token1 = $this->getTokenMatch());
68        $this->astNode->add(RuleType::_Empty, $token2 = $this->getTokenMatch());
69
70        $tokens = $this->astNode->getTokenMatches(TokenType::Empty);
71
72        self::assertSame([$token1, $token2], $tokens);
73    }
74
75    public function testItThrowsWhenGettingATokenThatIsNotAdded(): void
76    {
77        $this->expectException(\LogicException::class);
78
79        $this->astNode->getTokenMatch(TokenType::Empty);
80    }
81
82    public function testItGetsTheFirstTokenWhenSomeAreAdded(): void
83    {
84        $this->astNode->add(RuleType::_Empty, $token1 = $this->getTokenMatch());
85        $this->astNode->add(RuleType::_Empty, $this->getTokenMatch());
86
87        $token = $this->astNode->getTokenMatch(TokenType::Empty);
88
89        self::assertSame($token1, $token);
90    }
91
92    private function getTokenMatch(): TokenMatch
93    {
94        return new TokenMatch(TokenType::Other, (new GherkinDialectProvider())->getDefaultDialect(), 100, 'keyword', 'text', [], new Location(1, 1));
95    }
96}
97
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Trigger getSingle code on LambdaTest Cloud Grid

Execute automation tests with getSingle on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)