Skip to content

Commit e5a0230

Browse files
committed
Add errorSnippet method
1 parent f0cdd88 commit e5a0230

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/Maml.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Maml\Ast\IntegerNode;
1313
use Maml\Ast\NullNode;
1414
use Maml\Ast\ObjectNode;
15+
use Maml\Ast\Position;
1516
use Maml\Ast\RawStringNode;
1617
use Maml\Ast\StringNode;
1718

@@ -69,4 +70,18 @@ public static function toValue(
6970
}
7071
return $result;
7172
}
73+
74+
public static function errorSnippet(string $source, Position $pos, string $message): string
75+
{
76+
$offset = $pos->offset;
77+
$pre = \substr($source, \max(0, $offset - 40), \min($offset, 40));
78+
$lines = \explode("\n", $pre);
79+
$lastLine = \end($lines) ?: '';
80+
$postParts = \explode("\n", \substr($source, $offset, 40), 2);
81+
$postfix = $postParts[0];
82+
83+
$snippet = " {$lastLine}{$postfix}\n";
84+
$pointer = ' ' . \str_repeat('.', \max(0, \strlen($lastLine) - 1)) . "^\n";
85+
return "{$message} on line {$pos->line}.\n\n{$snippet}{$pointer}";
86+
}
7287
}

tests/MamlTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Maml\Tests;
66

7+
use Maml\Ast\ObjectNode;
78
use Maml\Maml;
89
use PHPUnit\Framework\TestCase;
910

@@ -20,4 +21,44 @@ public function testParseAndStringify(): void
2021
$reparsed = Maml::parse($stringified);
2122
$this->assertSame($parsed, $reparsed);
2223
}
24+
25+
public function testErrorSnippetPointsAtPosition(): void
26+
{
27+
$source = "{\n name: \"test\"\n timeout: -1\n}";
28+
$doc = Maml::parseAst($source);
29+
$this->assertInstanceOf(ObjectNode::class, $doc->value);
30+
$node = $doc->value->properties[1]->value;
31+
32+
$result = Maml::errorSnippet($source, $node->span->start, 'Invalid value');
33+
$this->assertStringContainsString('Invalid value on line 3.', $result);
34+
$this->assertStringContainsString('timeout: -1', $result);
35+
$this->assertStringContainsString('^', $result);
36+
}
37+
38+
public function testErrorSnippetAtStartOfSource(): void
39+
{
40+
$source = 'null';
41+
$doc = Maml::parseAst($source);
42+
43+
$result = Maml::errorSnippet($source, $doc->value->span->start, 'Expected object');
44+
$this->assertStringContainsString('Expected object on line 1.', $result);
45+
$this->assertStringContainsString('null', $result);
46+
$this->assertStringContainsString('^', $result);
47+
}
48+
49+
public function testErrorSnippetOnNestedNode(): void
50+
{
51+
$source = "{\n items: [\n {name: \"x\", count: 0}\n ]\n}";
52+
$doc = Maml::parseAst($source);
53+
$this->assertInstanceOf(ObjectNode::class, $doc->value);
54+
$items = $doc->value->properties[0]->value;
55+
$this->assertInstanceOf(\Maml\Ast\ArrayNode::class, $items);
56+
$inner = $items->elements[0]->value;
57+
$this->assertInstanceOf(ObjectNode::class, $inner);
58+
$countNode = $inner->properties[1]->value;
59+
60+
$result = Maml::errorSnippet($source, $countNode->span->start, 'Count must be positive');
61+
$this->assertStringContainsString('Count must be positive on line 3.', $result);
62+
$this->assertStringContainsString('^', $result);
63+
}
2364
}

0 commit comments

Comments
 (0)