Skip to content

Commit 19ea6b4

Browse files
authored
Hack in support for TextContentContentChangedEvent node (#25)
Add some hacky support for union type literals and partially ad-hoc support for the TextContentChangedEvents.
1 parent 1f7d721 commit 19ea6b4

File tree

6 files changed

+273
-7
lines changed

6 files changed

+273
-7
lines changed

src/DidChangeTextDocumentParams.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ class DidChangeTextDocumentParams
3333
* - apply the `TextDocumentContentChangeEvent`s in a single notification in the order
3434
* you receive them.
3535
*
36-
* @var array<array{range:Range,rangeLength:int,text:string}|array{text:string}>
36+
* @var array<TextDocumentContentChangeIncrementalEvent|TextDocumentContentChangeFullEvent>
3737
*/
3838
public $contentChanges;
3939

4040
/**
4141
* @param VersionedTextDocumentIdentifier $textDocument
42-
* @param array<array{range:Range,rangeLength:int,text:string}|array{text:string}> $contentChanges
42+
* @param array<TextDocumentContentChangeIncrementalEvent|TextDocumentContentChangeFullEvent> $contentChanges
4343
*/
4444
public function __construct(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
4545
{
@@ -55,7 +55,7 @@ public static function fromArray(array $array, bool $allowUnknownKeys = false)
5555
{
5656
$map = [
5757
'textDocument' => ['names' => [VersionedTextDocumentIdentifier::class], 'iterable' => false],
58-
'contentChanges' => ['names' => [], 'iterable' => true],
58+
'contentChanges' => ['names' => [TextDocumentContentChangeIncrementalEvent::class, TextDocumentContentChangeFullEvent::class], 'iterable' => true],
5959
];
6060

6161
foreach ($array as $key => &$value) {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php // Auto-generated from vscode-languageserver-protocol (typescript)
2+
3+
namespace Phpactor\LanguageServerProtocol;
4+
5+
use DTL\Invoke\Invoke;
6+
use Exception;
7+
use RuntimeException;
8+
9+
class TextDocumentContentChangeFullEvent
10+
{
11+
/**
12+
* The new text of the whole document.
13+
*
14+
* @var string
15+
*/
16+
public $text;
17+
18+
/**
19+
* @param string $text
20+
*/
21+
public function __construct(string $text)
22+
{
23+
$this->text = $text;
24+
}
25+
26+
/**
27+
* @param array<string,mixed> $array
28+
* @return self
29+
*/
30+
public static function fromArray(array $array, bool $allowUnknownKeys = false)
31+
{
32+
$map = [
33+
'text' => ['names' => [], 'iterable' => false],
34+
];
35+
36+
foreach ($array as $key => &$value) {
37+
if (!isset($map[$key])) {
38+
if ($allowUnknownKeys) {
39+
unset($array[$key]);
40+
continue;
41+
}
42+
43+
throw new RuntimeException(sprintf(
44+
'Parameter "%s" on class "%s" not known, known parameters: "%s"',
45+
$key,
46+
self::class,
47+
implode('", "', array_keys($map))
48+
));
49+
}
50+
51+
// from here we only care about arrays that can be transformed into
52+
// objects
53+
if (!is_array($value)) {
54+
continue;
55+
}
56+
57+
if (empty($map[$key]['names'])) {
58+
continue;
59+
}
60+
61+
if ($map[$key]['iterable']) {
62+
$value = array_map(function ($object) use ($map, $key, $allowUnknownKeys) {
63+
if (!is_array($object)) {
64+
return $object;
65+
}
66+
67+
return self::invokeFromNames($map[$key]['names'], $object, $allowUnknownKeys) ?: $object;
68+
}, $value);
69+
continue;
70+
}
71+
72+
$names = $map[$key]['names'];
73+
$value = self::invokeFromNames($names, $value, $allowUnknownKeys) ?: $value;
74+
}
75+
76+
return Invoke::new(self::class, $array);
77+
}
78+
79+
/**
80+
* @param array<string> $classNames
81+
* @param array<string,mixed> $object
82+
*/
83+
private static function invokeFromNames(array $classNames, array $object, bool $allowUnknownKeys): ?object
84+
{
85+
$lastException = null;
86+
foreach ($classNames as $className) {
87+
try {
88+
// @phpstan-ignore-next-line
89+
return call_user_func_array($className . '::fromArray', [$object, $allowUnknownKeys]);
90+
} catch (Exception $exception) {
91+
$lastException = $exception;
92+
continue;
93+
}
94+
}
95+
96+
throw $lastException;
97+
}
98+
99+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php // Auto-generated from vscode-languageserver-protocol (typescript)
2+
3+
namespace Phpactor\LanguageServerProtocol;
4+
5+
use DTL\Invoke\Invoke;
6+
use Exception;
7+
use RuntimeException;
8+
9+
class TextDocumentContentChangeIncrementalEvent
10+
{
11+
/**
12+
* The range of the document that changed.
13+
*
14+
* @var Range
15+
*/
16+
public $range;
17+
18+
/**
19+
* The optional length of the range that got replaced.
20+
*
21+
* @var int|null
22+
*/
23+
public $rangeLength;
24+
25+
/**
26+
* The new text for the provided range.
27+
*
28+
* @var string
29+
*/
30+
public $text;
31+
32+
/**
33+
* @param Range $range
34+
* @param int|null $rangeLength
35+
* @param string $text
36+
*/
37+
public function __construct(Range $range, string $text, ?int $rangeLength = null)
38+
{
39+
$this->range = $range;
40+
$this->rangeLength = $rangeLength;
41+
$this->text = $text;
42+
}
43+
44+
/**
45+
* @param array<string,mixed> $array
46+
* @return self
47+
*/
48+
public static function fromArray(array $array, bool $allowUnknownKeys = false)
49+
{
50+
$map = [
51+
'range' => ['names' => [Range::class], 'iterable' => false],
52+
'rangeLength' => ['names' => [], 'iterable' => false],
53+
'text' => ['names' => [], 'iterable' => false],
54+
];
55+
56+
foreach ($array as $key => &$value) {
57+
if (!isset($map[$key])) {
58+
if ($allowUnknownKeys) {
59+
unset($array[$key]);
60+
continue;
61+
}
62+
63+
throw new RuntimeException(sprintf(
64+
'Parameter "%s" on class "%s" not known, known parameters: "%s"',
65+
$key,
66+
self::class,
67+
implode('", "', array_keys($map))
68+
));
69+
}
70+
71+
// from here we only care about arrays that can be transformed into
72+
// objects
73+
if (!is_array($value)) {
74+
continue;
75+
}
76+
77+
if (empty($map[$key]['names'])) {
78+
continue;
79+
}
80+
81+
if ($map[$key]['iterable']) {
82+
$value = array_map(function ($object) use ($map, $key, $allowUnknownKeys) {
83+
if (!is_array($object)) {
84+
return $object;
85+
}
86+
87+
return self::invokeFromNames($map[$key]['names'], $object, $allowUnknownKeys) ?: $object;
88+
}, $value);
89+
continue;
90+
}
91+
92+
$names = $map[$key]['names'];
93+
$value = self::invokeFromNames($names, $value, $allowUnknownKeys) ?: $value;
94+
}
95+
96+
return Invoke::new(self::class, $array);
97+
}
98+
99+
/**
100+
* @param array<string> $classNames
101+
* @param array<string,mixed> $object
102+
*/
103+
private static function invokeFromNames(array $classNames, array $object, bool $allowUnknownKeys): ?object
104+
{
105+
$lastException = null;
106+
foreach ($classNames as $className) {
107+
try {
108+
// @phpstan-ignore-next-line
109+
return call_user_func_array($className . '::fromArray', [$object, $allowUnknownKeys]);
110+
} catch (Exception $exception) {
111+
$lastException = $exception;
112+
continue;
113+
}
114+
}
115+
116+
throw $lastException;
117+
}
118+
119+
}

ts/nodeMap.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import {
77
isIntersectionTypeNode,
88
isModuleDeclaration,
99
ModuleDeclaration,
10-
SyntaxKind,
1110
TypeLiteralNode,
12-
isTypeLiteralNode
11+
isTypeLiteralNode,
12+
isUnionTypeNode
1313
} from 'typescript';
1414
import {isNull} from 'util';
1515
import {constantsFromModule} from './phpClass';
1616

1717
export class NodeMap {
1818
aliases: TypeAliasMap = new TypeAliasMap();
1919
intersections: IntersectionMap = new IntersectionMap();
20+
unions: UnionMap = new UnionMap();
2021
interfaces: InterfaceMap = new InterfaceMap();
2122
modules: ModuleMap = new ModuleMap();
2223
typeLiterals = new TypeLiteralMap();
@@ -31,6 +32,8 @@ class TypeAliasMap extends Map<string, TypeNode> {
3132

3233
class IntersectionMap extends Map<string, TypeNode> {
3334
}
35+
class UnionMap extends Map<string, string[]> {
36+
}
3437
class TypeLiteralMap extends Map<string, TypeLiteralNode> {
3538
}
3639

@@ -58,7 +61,39 @@ export function createNodeMap(nodes: Node[], filter: RegExp = null): NodeMap {
5861
return;
5962
}
6063

61-
if (['InlayHint', 'InlineValueText', 'InlineValueVariableLookup', 'InlineValueEvaluatableExpression'].includes(node.name.escapedText.toString())) {
64+
if (isUnionTypeNode(node.type)) {
65+
if (node.name.escapedText.toString() ==='TextDocumentContentChangeEvent') {
66+
67+
const incremental = node.type.types[0];
68+
const full = node.type.types[1];
69+
70+
if (!isTypeLiteralNode(incremental) || !isTypeLiteralNode(full)) {
71+
return;
72+
}
73+
74+
map.typeLiterals.set(
75+
'TextDocumentContentChangeIncrementalEvent',
76+
incremental
77+
);
78+
map.typeLiterals.set(
79+
'TextDocumentContentChangeFullEvent',
80+
full
81+
);
82+
map.unions.set(node.name.escapedText.toString(), [
83+
'TextDocumentContentChangeIncrementalEvent',
84+
'TextDocumentContentChangeFullEvent'
85+
]);
86+
87+
return;
88+
}
89+
}
90+
91+
if ([
92+
'InlayHint',
93+
'InlineValueText',
94+
'InlineValueVariableLookup',
95+
'InlineValueEvaluatableExpression'
96+
].includes(node.name.escapedText.toString())) {
6297
if (isTypeLiteralNode(node.type)) {
6398
map.typeLiterals.set(node.name.escapedText.toString(), node.type);
6499
}

ts/phpClass.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
isExpressionWithTypeArguments,
2424
TypeLiteralNode,
2525
NodeArray,
26-
TypeElement
26+
TypeElement,
27+
UnionTypeNode
2728
} from 'typescript';
2829

2930
import { PhpType, TypeConverter } from './typeConverter';

ts/typeConverter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ export class TypeConverter
113113
return new PhpType(null, typeName + '::*');
114114
}
115115

116+
if (this.nodeMap.unions.has(typeName)) {
117+
const classNames = this.nodeMap.unions.get(typeName);
118+
const phpTypes = classNames.map(name => {
119+
return this.phpType(this.nodeMap.typeLiterals.get(name));
120+
});
121+
return new PhpType(
122+
null,
123+
classNames.map((type: string) => { return type; }).join('|'),
124+
classNames as ClassName[]
125+
);
126+
}
127+
116128
if (this.nodeMap.aliases.has(typeName)) {
117129
return this.phpType(this.nodeMap.aliases.get(typeName));
118130
}

0 commit comments

Comments
 (0)