Skip to content

Commit b966e3d

Browse files
authored
Merge pull request #207 from DorZuberi/add-typescript-language
2 parents d499fe3 + 5c19bd3 commit b966e3d

9 files changed

Lines changed: 314 additions & 0 deletions

File tree

src/Highlighter.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Tempest\Highlight\Languages\Terraform\TerraformLanguage;
3232
use Tempest\Highlight\Languages\Text\TextLanguage;
3333
use Tempest\Highlight\Languages\Twig\TwigLanguage;
34+
use Tempest\Highlight\Languages\TypeScript\TypeScriptLanguage;
3435
use Tempest\Highlight\Languages\Xml\XmlLanguage;
3536
use Tempest\Highlight\Languages\Yaml\YamlLanguage;
3637
use Tempest\Highlight\Themes\CssTheme;
@@ -79,6 +80,7 @@ public function __construct(private readonly Theme $theme = new CssTheme())
7980
->addLanguage(new SqlLanguage())
8081
->addLanguage(new TerminalLanguage())
8182
->addLanguage(new TerraformLanguage())
83+
->addLanguage(new TypeScriptLanguage())
8284
->addLanguage(new XmlLanguage())
8385
->addLanguage(new YamlLanguage())
8486
->addLanguage(new DotEnvLanguage())
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Languages\TypeScript\Patterns;
6+
7+
use Tempest\Highlight\IsPattern;
8+
use Tempest\Highlight\Pattern;
9+
use Tempest\Highlight\Tokens\TokenTypeEnum;
10+
11+
final class TsBuiltInTypePattern implements Pattern
12+
{
13+
use IsPattern;
14+
15+
public function getPattern(): string
16+
{
17+
return '/\b(?<!\.)(?<match>string|number|boolean|any|unknown|never|bigint|symbol|object|undefined)\b/';
18+
}
19+
20+
public function getTokenType(): TokenTypeEnum
21+
{
22+
return TokenTypeEnum::TYPE;
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Languages\TypeScript\Patterns;
6+
7+
use Tempest\Highlight\IsPattern;
8+
use Tempest\Highlight\Pattern;
9+
use Tempest\Highlight\Tokens\TokenTypeEnum;
10+
11+
final class TsDecoratorPattern implements Pattern
12+
{
13+
use IsPattern;
14+
15+
public function getPattern(): string
16+
{
17+
return '/(?<match>@[A-Za-z_][\w]*)/';
18+
}
19+
20+
public function getTokenType(): TokenTypeEnum
21+
{
22+
return TokenTypeEnum::ATTRIBUTE;
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Languages\TypeScript\Patterns;
6+
7+
use Tempest\Highlight\IsPattern;
8+
use Tempest\Highlight\Pattern;
9+
use Tempest\Highlight\Tokens\TokenTypeEnum;
10+
11+
final class TsGenericPattern implements Pattern
12+
{
13+
use IsPattern;
14+
15+
public function getPattern(): string
16+
{
17+
return '/(?<=\w)(?<match><[A-Z][\w\s,\.\[\]]*>)/';
18+
}
19+
20+
public function getTokenType(): TokenTypeEnum
21+
{
22+
return TokenTypeEnum::GENERIC;
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Languages\TypeScript\Patterns;
6+
7+
use Tempest\Highlight\IsPattern;
8+
use Tempest\Highlight\Pattern;
9+
use Tempest\Highlight\Tokens\TokenTypeEnum;
10+
11+
final class TsTypeAnnotationPattern implements Pattern
12+
{
13+
use IsPattern;
14+
15+
public function getPattern(): string
16+
{
17+
return '/(?<=[\w\)\]])\s*:\s*(?<match>[A-Z][\w]*(?:\.[A-Z][\w]*)*(?:\[\])?)/';
18+
}
19+
20+
public function getTokenType(): TokenTypeEnum
21+
{
22+
return TokenTypeEnum::TYPE;
23+
}
24+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Languages\TypeScript;
6+
7+
use Override;
8+
use Tempest\Highlight\Languages\JavaScript\JavaScriptLanguage;
9+
use Tempest\Highlight\Languages\JavaScript\Patterns\CombinedJsKeywordPattern;
10+
use Tempest\Highlight\Languages\TypeScript\Patterns\TsBuiltInTypePattern;
11+
use Tempest\Highlight\Languages\TypeScript\Patterns\TsDecoratorPattern;
12+
use Tempest\Highlight\Languages\TypeScript\Patterns\TsGenericPattern;
13+
use Tempest\Highlight\Languages\TypeScript\Patterns\TsTypeAnnotationPattern;
14+
15+
class TypeScriptLanguage extends JavaScriptLanguage
16+
{
17+
private const array TS_KEYWORDS = [
18+
'type', 'declare', 'readonly', 'namespace', 'keyof', 'infer',
19+
'satisfies', 'abstract', 'is', 'module', 'override', 'asserts',
20+
];
21+
22+
#[Override]
23+
public function getName(): string
24+
{
25+
return 'ts';
26+
}
27+
28+
#[Override]
29+
public function getAliases(): array
30+
{
31+
return ['typescript'];
32+
}
33+
34+
#[Override]
35+
public function getPatterns(): array
36+
{
37+
return [
38+
...parent::getPatterns(),
39+
new CombinedJsKeywordPattern(self::TS_KEYWORDS),
40+
new TsBuiltInTypePattern(),
41+
new TsTypeAnnotationPattern(),
42+
new TsDecoratorPattern(),
43+
new TsGenericPattern(),
44+
];
45+
}
46+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* User service with basic CRUD operations.
3+
*/
4+
import { Injectable } from '@angular/core';
5+
6+
interface User {
7+
id: number;
8+
name: string;
9+
email: string;
10+
roles: readonly string[];
11+
}
12+
13+
type UserId = number;
14+
15+
enum Role {
16+
Admin = 'admin',
17+
Member = 'member',
18+
Guest = 'guest',
19+
}
20+
21+
@Injectable({ providedIn: 'root' })
22+
export class UserService<T extends User> {
23+
private readonly users: Map<UserId, T> = new Map();
24+
25+
constructor(private readonly apiUrl: string) {}
26+
27+
public async findById(id: UserId): Promise<T | undefined> {
28+
const user = this.users.get(id);
29+
if (user === undefined) {
30+
return undefined;
31+
}
32+
return user;
33+
}
34+
35+
public async create(data: Omit<T, 'id'>): Promise<T> {
36+
const id: number = Math.floor(Math.random() * 1_000_000);
37+
const user = { id, ...data } as T;
38+
this.users.set(id, user);
39+
return user;
40+
}
41+
42+
public delete(id: UserId): boolean {
43+
return this.users.delete(id);
44+
}
45+
46+
public list(): readonly T[] {
47+
return Array.from(this.users.values());
48+
}
49+
}
50+
51+
const service = new UserService<User>('https://api.example.com');
52+
const admin: User = {
53+
id: 1,
54+
name: 'Alice',
55+
email: 'alice@example.com',
56+
roles: ['admin'],
57+
};

tests/Bench/HighlighterBench.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ final class HighlighterBench
3939
'sql' => 'sql.txt',
4040
'terminal' => 'terminal.txt',
4141
'terraform' => 'terraform.txt',
42+
'typescript' => 'typescript.txt',
4243
'twig' => 'twig.txt',
4344
'xml' => 'xml.txt',
4445
'yaml' => 'yaml.txt',
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Highlight\Tests\Languages\TypeScript;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\TestCase;
9+
use Tempest\Highlight\Highlighter;
10+
11+
class TypeScriptLanguageTest extends TestCase
12+
{
13+
#[DataProvider('provide_highlight_cases')]
14+
public function test_highlight(string $content, string $expected): void
15+
{
16+
$highlighter = new Highlighter();
17+
18+
$this->assertSame(
19+
$expected,
20+
$highlighter->parse($content, 'ts'),
21+
);
22+
23+
$this->assertSame(
24+
$expected,
25+
$highlighter->parse($content, 'typescript'),
26+
);
27+
}
28+
29+
public static function provide_highlight_cases(): iterable
30+
{
31+
return [
32+
[
33+
'type Alias = string;',
34+
'<span class="hl-keyword">type</span> Alias = <span class="hl-type">string</span>;',
35+
],
36+
[
37+
<<<'TXT'
38+
interface User {
39+
id: number;
40+
name: string;
41+
}
42+
TXT,
43+
<<<'TXT'
44+
<span class="hl-keyword">interface</span> User {
45+
<span class="hl-property">id</span>: <span class="hl-type">number</span>;
46+
<span class="hl-property">name</span>: <span class="hl-type">string</span>;
47+
}
48+
TXT,
49+
],
50+
[
51+
'const x: boolean = true;',
52+
'<span class="hl-keyword">const</span> <span class="hl-property">x</span>: <span class="hl-type">boolean</span> = <span class="hl-keyword">true</span>;',
53+
],
54+
[
55+
'readonly name: string;',
56+
'<span class="hl-keyword">readonly</span> <span class="hl-property">name</span>: <span class="hl-type">string</span>;',
57+
],
58+
[
59+
'function greet(name: string): void {}',
60+
'<span class="hl-keyword">function</span> <span class="hl-property">greet</span>(<span class="hl-property">name</span>: <span class="hl-type">string</span>): <span class="hl-keyword">void</span> {}',
61+
],
62+
[
63+
'let u: User = getUser();',
64+
'<span class="hl-keyword">let</span> <span class="hl-property">u</span>: <span class="hl-type">User</span> = <span class="hl-property">getUser</span>();',
65+
],
66+
[
67+
'function identity<T>(v: T): T {}',
68+
'<span class="hl-keyword">function</span> identity<span class="hl-generic">&lt;T&gt;</span>(<span class="hl-property">v</span>: <span class="hl-type">T</span>): <span class="hl-type">T</span> {}',
69+
],
70+
[
71+
'class Service<K, V extends Base> {}',
72+
'<span class="hl-keyword">class</span> <span class="hl-type">Service</span><span class="hl-generic">&lt;K, V <span class="hl-keyword">extends</span> Base&gt;</span> {}',
73+
],
74+
[
75+
<<<'TXT'
76+
@Component({ selector: 'x' })
77+
class Foo {}
78+
TXT,
79+
<<<'TXT'
80+
<span class="hl-attribute">@<span class="hl-property">Component</span></span>({ <span class="hl-property">selector</span>: <span class="hl-value">'x'</span> })
81+
<span class="hl-keyword">class</span> <span class="hl-type">Foo</span> {}
82+
TXT,
83+
],
84+
[
85+
<<<'TXT'
86+
@Injectable
87+
export class Bar {}
88+
TXT,
89+
<<<'TXT'
90+
<span class="hl-attribute">@Injectable</span>
91+
<span class="hl-keyword">export</span> <span class="hl-keyword">class</span> <span class="hl-type">Bar</span> {}
92+
TXT,
93+
],
94+
[
95+
<<<'TXT'
96+
/**
97+
* Greet a user.
98+
* @param {string} name
99+
*/
100+
function greet(name: string): void {}
101+
TXT,
102+
<<<'TXT'
103+
<span class="hl-comment">/**
104+
* Greet a user.
105+
* <span class="hl-value">@param</span> <span class="hl-type">{string}</span> <span class="hl-value">name</span>
106+
*/</span>
107+
<span class="hl-keyword">function</span> <span class="hl-property">greet</span>(<span class="hl-property">name</span>: <span class="hl-type">string</span>): <span class="hl-keyword">void</span> {}
108+
TXT,
109+
],
110+
];
111+
}
112+
}

0 commit comments

Comments
 (0)