Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Highlighter.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Tempest\Highlight\Languages\Python\PythonLanguage;
use Tempest\Highlight\Languages\Scss\ScssLanguage;
use Tempest\Highlight\Languages\Sql\SqlLanguage;
use Tempest\Highlight\Languages\Svelte\SvelteLanguage;
use Tempest\Highlight\Languages\Terminal\TerminalLanguage;
use Tempest\Highlight\Languages\Terraform\TerraformLanguage;
use Tempest\Highlight\Languages\Text\TextLanguage;
Expand Down Expand Up @@ -86,7 +87,8 @@ public function __construct(
->addLanguage(new YamlLanguage())
->addLanguage(new DotEnvLanguage())
->addLanguage(new IniLanguage())
->addLanguage(new TwigLanguage());
->addLanguage(new TwigLanguage())
->addLanguage(new SvelteLanguage());

$this->parseTokens = new ParseTokens();
$this->groupTokens = new GroupTokens();
Expand Down
4 changes: 3 additions & 1 deletion src/Languages/JavaScript/Patterns/JsMethodPattern.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: 'calcArea() {', output: 'calcArea')]
#[PatternTest(input: 'calc_area() {', output: 'calc_area')]
#[PatternTest(input: '$init() {', output: '$init')]
final readonly class JsMethodPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<match>[\w]+)\(';
return '(?<match>[\w\$]+)\(';
}

public function getTokenType(): TokenTypeEnum
Expand Down
24 changes: 24 additions & 0 deletions src/Languages/Svelte/Injections/SvelteBlockExpressionInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class SvelteBlockExpressionInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '\{[#@:\/](?:[a-z]+)\b\s*(?<match>(?:[^{}]++|\{(?&match)\})*+)\}';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'typescript');
}
}
29 changes: 29 additions & 0 deletions src/Languages/Svelte/Injections/SvelteExpressionInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;
use Tempest\Highlight\PatternTest;

#[PatternTest(input: '{role}', output: 'role')]
#[PatternTest(input: '{role.id}', output: 'role.id')]
#[PatternTest(input: 'onclick={() => count++}', output: '() => count++')]
#[PatternTest(input: 'onclick={() => { updateTab() }}', output: '() => { updateTab() }')]
final class SvelteExpressionInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '\{(?<match>[^#@:\/{}](?:[^{}]++|\{(?&match)\})*+)\}';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'typescript');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Svelte/Injections/SvelteTypeScriptInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class SvelteTypeScriptInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '<script\s+lang="ts">(?<match>[\s\S]*?)<\/script>';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'typescript');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Svelte/Patterns/SvelteBlockKeywordPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class SvelteBlockKeywordPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '\{(?<match>[#@:\/]\w+)\b';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::KEYWORD;
}
}
24 changes: 24 additions & 0 deletions src/Languages/Svelte/Patterns/SvelteDirectiveArgumentPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class SvelteDirectiveArgumentPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '\s(?:bind|use|transition|in|out|animate|on|class|style):(?<match>[\w-]+)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
24 changes: 24 additions & 0 deletions src/Languages/Svelte/Patterns/SvelteDirectivePattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class SvelteDirectivePattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<=\s)(?<match>(?:bind|use|transition|in|out|animate|on|class|style)):';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
46 changes: 46 additions & 0 deletions src/Languages/Svelte/SvelteLanguage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php


declare(strict_types=1);

namespace Tempest\Highlight\Languages\Svelte;

use Override;
use Tempest\Highlight\Languages\Html\HtmlLanguage;
use Tempest\Highlight\Languages\Svelte\Injections\SvelteBlockExpressionInjection;
use Tempest\Highlight\Languages\Svelte\Injections\SvelteExpressionInjection;
use Tempest\Highlight\Languages\Svelte\Injections\SvelteTypeScriptInjection;
use Tempest\Highlight\Languages\Svelte\Patterns\SvelteBlockKeywordPattern;
use Tempest\Highlight\Languages\Svelte\Patterns\SvelteDirectiveArgumentPattern;
use Tempest\Highlight\Languages\Svelte\Patterns\SvelteDirectivePattern;

class SvelteLanguage extends HtmlLanguage
{
#[Override]
public function getName(): string
{
return 'svelte';
}

#[Override]
public function getInjections(): array
{
return [
...parent::getInjections(),
new SvelteTypeScriptInjection(),
new SvelteExpressionInjection(),
new SvelteBlockExpressionInjection(),
];
}

#[Override]
public function getPatterns(): array
{
return [
...parent::getPatterns(),
new SvelteBlockKeywordPattern(),
new SvelteDirectivePattern(),
new SvelteDirectiveArgumentPattern(),
];
}
}
28 changes: 28 additions & 0 deletions src/Languages/TypeScript/Patterns/TsMethodPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\TypeScript\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\PatternTest;
use Tempest\Highlight\Tokens\TokenTypeEnum;

#[PatternTest(input: 'createList<Todo[]>(initial)', output: 'createList')]
#[PatternTest(input: "getRole<'user' | 'admin'>(initial)", output: 'getRole')]
#[PatternTest(input: 'getRole<"user" | "admin">(initial)', output: 'getRole')]
final readonly class TsMethodPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<match>[\w\$]+)<[A-Z\'"][\w\s,\.\[\]\'"|]*>\(';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
2 changes: 2 additions & 0 deletions src/Languages/TypeScript/TypeScriptLanguage.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Tempest\Highlight\Languages\TypeScript\Patterns\TsBuiltInTypePattern;
use Tempest\Highlight\Languages\TypeScript\Patterns\TsDecoratorPattern;
use Tempest\Highlight\Languages\TypeScript\Patterns\TsGenericPattern;
use Tempest\Highlight\Languages\TypeScript\Patterns\TsMethodPattern;
use Tempest\Highlight\Languages\TypeScript\Patterns\TsTypeAnnotationPattern;

class TypeScriptLanguage extends JavaScriptLanguage
Expand Down Expand Up @@ -41,6 +42,7 @@ public function getPatterns(): array
new TsTypeAnnotationPattern(),
new TsDecoratorPattern(),
new TsGenericPattern(),
new TsMethodPattern(),
];
}
}
93 changes: 93 additions & 0 deletions tests/Bench/Fixtures/svelte.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!-- A Svelte 5 counter-and-list component -->
<script lang="ts">
type Todo = { id: number; text: string; done: boolean };

let { initial = [], onchange }: { initial?: Todo[]; onchange?: (t: Todo[]) => void } = $props();

let draft = $state('');
let todos = $state<Todo[]>(initial);
let filter = $state<'all' | 'active' | 'done'>('all');

let count = $state(0);
let doubled = $derived(count * 2);

let visible = $derived.by(() => {
switch (filter) {
case 'active': return todos.filter((t) => !t.done);
case 'done': return todos.filter((t) => t.done);
default: return todos;
}
});

let remaining = $derived(todos.filter((t) => !t.done).length);

$effect(() => {
onchange?.(todos);
});

$inspect(remaining).with((type, value) => {
console.log(type, value);
});

function add() {
if (!draft.trim()) return;
todos.push({ id: Date.now(), text: draft, done: false });
draft = '';
}
</script>

<button onclick={() => count++}>
{doubled}
</button>

<form onsubmit={(e) => { e.preventDefault(); add(); }}>
<input bind:value={draft} placeholder="What needs doing?" />
<button type="submit">Add</button>
</form>

<select bind:value={filter}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="done">Done</option>
</select>

<ul>
{#each visible as todo (todo.id)}
<li class:done={todo.done}>
<input type="checkbox" bind:checked={todo.done} />
{todo.text}
</li>
{:else}
<li>Nothing here.</li>
{/each}
</ul>

{#if filter === 'all'}
<p>Showing everything.</p>
{:else if filter === 'active'}
<p>Showing active todos.</p>
{:else if filter === 'done'}
<p>Showing completed todos.</p>
{:else}
<p>Unknown filter.</p>
{/if}

<!-- nested-brace expressions inside block tags -->

{#each entries as { key, value }}
<dt>{key}</dt>
<dd>{value}</dd>
{/each}

{#each todos.filter((t) => ({ ...t, label: t.text.trim() })) as todo (todo.id)}
<li>{todo.label}</li>
{/each}

{#each Object.entries({ a: 1, b: 2, c: 3 }) as [key, value]}
<p>{key} = {value}</p>
{/each}

{#if items.some((x) => x.meta?.tags?.includes('urgent'))}
{@const summary = { count: items.length, urgent: true }}
<p>{summary.count} urgent items</p>
{/if}
1 change: 1 addition & 0 deletions tests/Bench/HighlighterBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ final class HighlighterBench
'python' => 'python.txt',
'scss' => 'scss.txt',
'sql' => 'sql.txt',
'svelte' => 'svelte.txt',
'terminal' => 'terminal.txt',
'terraform' => 'terraform.txt',
'typescript' => 'typescript.txt',
Expand Down
Loading
Loading