Skip to content

Commit 1bacbdb

Browse files
committed
added type detecting to context
Added specs for ContextResolver Added NodeTypeResolver Added Scope variables detecting
1 parent 6b05288 commit 1bacbdb

23 files changed

Lines changed: 887 additions & 245 deletions

.travis.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ language: php
22
php:
33
- 5.5
44
- 5.6
5-
- nightly
5+
matrix:
6+
allow_failures:
7+
- php:
8+
- 7
9+
- hhvm
610
install:
711
- composer install
812
script: bin/peridot specs/ && bin/behat
Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,95 @@
11
<?php
22

33
use Complete\Resolver\ContextResolver;
4+
use Complete\Resolver\NodeTypeResolver;
45
use Parser\ErrorFreePhpParser;
6+
use Parser\UseParser;
57
use PhpParser\Lexer;
68
use Entity\Completion\Context;
9+
use Entity\Index;
10+
use Monolog\Logger;
11+
use Monolog\Handler\NullHandler;
712

813
describe('ContextResolver', function(){
914
beforeEach(function(){
15+
$logger = new Logger('spec');
16+
$logger->pushHandler(new NullHandler);
17+
$this->index = new Index;
1018
$this->parser = new ErrorFreePhpParser(new Lexer);
11-
$this->resolver = new ContextResolver($this->parser);
19+
$this->typeResolver = new NodeTypeResolver($logger, new UseParser);
20+
$this->resolver = new ContextResolver($this->parser, $this->typeResolver, $logger);
1221
$this->dummyLine = '$obj->getMethod()->';
1322
});
14-
describe('getContext()', function(){
23+
describe('->getContext()', function(){
1524
it('throws exception on empty line', function(){
16-
expect(function($l){ $this->resolver->getContext($l); })->with('')->to->throw('Exception');
25+
expect([$this->resolver, 'getContext'])
26+
->with('', $this->index)->to->throw('Exception');
1727
});
1828
it('returns Context instance', function(){
1929
$result = $this->resolver->getContext($this->dummyLine);
2030
expect($result)->to->be->an->instanceof(Context::class);
2131
});
32+
describe('Namespace', function(){
33+
it('has type namespace after namespace symbol', function(){
34+
$context = $this->resolver->getContext('namespace ');
35+
expect($context->isNamespace())->to->be->true;
36+
});
37+
it('has type namespace after namespace symbol with TString', function(){
38+
$context = $this->resolver->getContext('namespace SomeName');
39+
expect($context->isNamespace())->to->be->true;
40+
});
41+
it('has type namespace after namespace symbol with TString and separator', function(){
42+
$context = $this->resolver->getContext('namespace SomeName\AndOther\Name');
43+
expect($context->isNamespace())->to->be->true;
44+
});
45+
it('hasn\'t type namespace after ;', function(){
46+
$context = $this->resolver->getContext('namespace SomeName\AndOther\Name;');
47+
expect($context->isNamespace())->to->be->false;
48+
});
49+
});
50+
describe('Use', function(){
51+
it('has type use after use symbol', function(){
52+
$context = $this->resolver->getContext('use ');
53+
expect($context->isUse())->to->be->true;
54+
});
55+
it('has type use after use symbol with TString', function(){
56+
$context = $this->resolver->getContext('use SomeName');
57+
expect($context->isUse())->to->be->true;
58+
});
59+
it('has type use after use symbol with TString and separator', function(){
60+
$context = $this->resolver->getContext('use SomeName\AndOther\Name');
61+
expect($context->isUse())->to->be->true;
62+
});
63+
it('hasn\'t type use after ;', function(){
64+
$context = $this->resolver->getContext('use SomeName\AndOther\Name;');
65+
expect($context->isUse())->to->be->false;
66+
});
67+
});
68+
describe('Object', function(){
69+
it('has type object after object operator', function(){
70+
$context = $this->resolver->getContext('$var->');
71+
expect($context->isObject())->to->be->true;
72+
});
73+
it('has type object after object operator and $this', function(){
74+
$context = $this->resolver->getContext('$this->');
75+
expect($context->isObject())->to->be->true;
76+
});
77+
it('has type object after object operator with complex prefix', function(){
78+
$context = $this->resolver->getContext('$var->getMethod()->param->');
79+
expect($context->isObject())->to->be->true;
80+
});
81+
it('has type object after object operator with TString', function(){
82+
$context = $this->resolver->getContext('$var->param');
83+
expect($context->isObject())->to->be->true;
84+
});
85+
it('hasn\'t type object after object operator with TString and separator', function(){
86+
$context = $this->resolver->getContext('$var->param;');
87+
expect($context->isObject())->to->be->false;
88+
});
89+
it('hasn\'t type object after object operator with TString and space', function(){
90+
$context = $this->resolver->getContext('$var->param ');
91+
expect($context->isObject())->to->be->false;
92+
});
93+
});
2294
});
2395
});
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
use Complete\Resolver\NodeTypeResolver;
4+
use Entity\Completion\Scope;
5+
use Entity\FQCN;
6+
use Entity\Index;
7+
use Entity\Node\ClassData;
8+
use Entity\Node\ClassProperty;
9+
use Entity\Node\MethodData;
10+
use Entity\Node\Variable;
11+
use Parser\UseParser;
12+
use PhpParser\Node\Expr\Variable as NodeVar;
13+
use PhpParser\Node\Expr\PropertyFetch;
14+
use PhpParser\Node\Expr\MethodCall;
15+
use Monolog\Logger;
16+
use Monolog\Handler\NullHandler;
17+
18+
function createClass($classFQN, $fqcn){
19+
$class = new ClassData($classFQN, 'dummy/path/class.php');
20+
$method = new MethodData('method2');
21+
$param = new ClassProperty('param2');
22+
$param->type = $fqcn;
23+
$method->setReturn($fqcn);
24+
$class->addMethod($method);
25+
$class->addProp($param);
26+
return $class;
27+
}
28+
29+
describe('NodeTypeResolver', function(){
30+
beforeEach(function(){
31+
$logger = new Logger('spec');
32+
$logger->pushHandler(new NullHandler);
33+
$this->resolver = new NodeTypeResolver($logger, new UseParser);
34+
$this->scope = new Scope;
35+
$this->index = new Index;
36+
$this->var = new Variable('test');
37+
$fqcn = new FQCN('ClassName', 'Some\\Path');
38+
$fqcn2 = new FQCN('AnotherClassName', 'Another\\Path\\To\\It');
39+
$this->anotherFQCN = $fqcn2;
40+
$this->var->setType($fqcn);
41+
$this->scope->addVar($this->var);
42+
$class = createClass($fqcn, $fqcn2);
43+
$class2 = createClass($fqcn2, $fqcn);
44+
$this->index->addClass($class);
45+
$this->index->addClass($class2);
46+
});
47+
describe('->getType()', function(){
48+
it('returns variable type from scope', function(){
49+
$node = new NodeVar;
50+
$node->name = $this->var->getName();
51+
expect($this->resolver->getChainType($node, $this->index, $this->scope))
52+
->to->equal($this->var->getType());
53+
});
54+
describe('Properties', function(){
55+
beforeEach(function(){
56+
$this->node = new PropertyFetch;
57+
$this->node->var = new NodeVar;
58+
$this->node->var->name = $this->var->getName();
59+
});
60+
it('returns null for unknown property', function(){
61+
$this->node->name = 'param';
62+
expect($this->resolver->getChainType($this->node, $this->index, $this->scope))
63+
->to->be->null;
64+
});
65+
it('returns type for known property', function(){
66+
$this->node->name = 'param2';
67+
expect($this->resolver->getChainType($this->node, $this->index, $this->scope))
68+
->to->equal($this->anotherFQCN);
69+
});
70+
});
71+
describe('Method', function(){
72+
beforeEach(function(){
73+
$this->node = new MethodCall;
74+
$this->node->var = new NodeVar;
75+
$this->node->var->name = $this->var->getName();
76+
});
77+
it('returns null for unknown method', function(){
78+
$this->node->name = 'method';
79+
expect($this->resolver->getChainType($this->node, $this->index, $this->scope))
80+
->to->be->null;
81+
});
82+
it('returns type for known method', function(){
83+
$this->node->name = 'method2';
84+
expect($this->resolver->getChainType($this->node, $this->index, $this->scope))
85+
->to->equal($this->anotherFQCN);
86+
});
87+
});
88+
describe('Complex', function(){
89+
beforeEach(function(){
90+
$this->node = new MethodCall;
91+
$this->node->name = 'method2';
92+
$this->node->var = new PropertyFetch;
93+
$this->node->var->name = 'param2';
94+
$this->node->var->var = new MethodCall;
95+
$this->node->var->var->name = 'method2';
96+
$this->node->var->var->var = new NodeVar;
97+
$this->node->var->var->var->name = $this->var->getName();
98+
});
99+
it('returns type for known property in complex chain', function(){
100+
$node = new PropertyFetch;
101+
$node->var = $this->node;
102+
$node->name = 'param2';
103+
expect($this->resolver->getChainType($node, $this->index, $this->scope))
104+
->to->equal($this->var->getType());
105+
});
106+
});
107+
});
108+
});

specs/entity/FQCN.spec.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
use Entity\FQCN;
4+
use Entity\FQN;
5+
6+
describe('FQCN', function(){
7+
describe('__construct()', function(){
8+
it('creates parts from string with class', function(){
9+
$fqn = new FQCN('SomeClassName', 'Some\\Long\\Parts\\To\\Name');
10+
expect($fqn->getParts())->to->equal([
11+
'Some',
12+
'Long',
13+
'Parts',
14+
'To',
15+
'Name',
16+
'SomeClassName'
17+
]);
18+
});
19+
it('creates parts from array', function(){
20+
$parts = ['Some', 'Long', 'Parts'];
21+
$fqn = new FQCN('ClassName', $parts);
22+
$parts[] = 'ClassName';
23+
expect($fqn->getParts())->to->equal($parts);
24+
});
25+
it('FQCN with class name only', function(){
26+
$fqn = new FQN('ClassName');
27+
expect($fqn->getParts())->to->equal(['ClassName']);
28+
});
29+
});
30+
describe('->join()', function(){
31+
it('joins another FQCN', function(){
32+
$fqn = new FQCN('ClassName', 'Some\\Long\\Path');
33+
$join = new FQCN('AnotherName', 'Another\\Long\\Name');
34+
expect($fqn->join($join)->getParts())->to->equal([
35+
'Some',
36+
'Long',
37+
'Path',
38+
'ClassName',
39+
'Another',
40+
'Long',
41+
'Name',
42+
'AnotherName'
43+
]);
44+
});
45+
it('joins FQN', function(){
46+
$fqn = new FQCN('ClassName', 'Some\\Long\\Path\\Another');
47+
$join = new FQN('Another\\Long\\Name');
48+
expect($fqn->join($join)->getParts())->to->equal([
49+
'Some',
50+
'Long',
51+
'Path',
52+
'Another',
53+
'ClassName',
54+
'Another',
55+
'Long',
56+
'Name'
57+
]);
58+
});
59+
});
60+
describe('->toString()', function(){
61+
it('returns valid string', function(){
62+
$str = 'Some\\Long\\Path\\To\\Name';
63+
$fqn = new FQCN('ClassName', $str);
64+
expect($fqn->toString())->to->equal($str . '\\ClassName');
65+
});
66+
});
67+
});

specs/entity/FQN.spec.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
use Entity\FQN;
4+
5+
describe('FQN', function(){
6+
describe('__construct()', function(){
7+
it('creates parts from string', function(){
8+
$fqn = new FQN('Some\\Long\\Parts\\To\\Name');
9+
expect($fqn->getParts())->to->equal([
10+
'Some',
11+
'Long',
12+
'Parts',
13+
'To',
14+
'Name'
15+
]);
16+
});
17+
it('creates parts from array', function(){
18+
$parts = ['Some', 'Long', 'Parts'];
19+
$fqn = new FQN($parts);
20+
expect($fqn->getParts())->to->equal($parts);
21+
});
22+
it('creates empty parts on empty call', function(){
23+
$fqn = new FQN;
24+
expect($fqn->getParts())->to->equal([]);
25+
});
26+
});
27+
describe('->join()', function(){
28+
it('joins another FQN', function(){
29+
$fqn = new FQN('Some\\Long\\Path');
30+
$join = new FQN('Another\\Long\\Name');
31+
expect($fqn->join($join)->getParts())->to->equal([
32+
'Some',
33+
'Long',
34+
'Path',
35+
'Another',
36+
'Long',
37+
'Name'
38+
]);
39+
});
40+
it('joins child FQN', function(){
41+
$fqn = new FQN('Some\\Long\\Path\\Another');
42+
$join = new FQN('Another\\Long\\Name');
43+
expect($fqn->join($join)->getParts())->to->equal([
44+
'Some',
45+
'Long',
46+
'Path',
47+
'Another',
48+
'Long',
49+
'Name'
50+
]);
51+
});
52+
});
53+
describe('->toString()', function(){
54+
it('returns valid string', function(){
55+
$str = 'Some\\Long\\Path\\To\\Name';
56+
$fqn = new FQN($str);
57+
expect($fqn->toString())->to->equal($str);
58+
});
59+
});
60+
});

src/Complete/CompleteEngine.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public function createCompletion(
7676
];
7777
}
7878
protected function findEntries(Project $project, Scope $scope, $badLine, $column, $lines){
79-
$context = $this->contextResolver->getContext($badLine, $column);
80-
return $this->completer->getEntries($project, $context, $scope);
79+
$context = $this->contextResolver->getContext($badLine, $project->getIndex(), $scope);
80+
return $this->completer->getEntries($project, $context);
8181
}
8282
/**
8383
* @TODO

src/Complete/Completer/ClassNameCompleter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class ClassNameCompleter{
1010
public function getEntries(Project $project, Context $context){
1111
$entries = [];
12-
$postfix = trim($context->getPostfix());
12+
$postfix = trim($context->getData());
1313
foreach($project->getIndex()->getClasses() as $fqcn => $class){
1414
if(!empty($postfix) && strpos($fqcn, $postfix) === false){
1515
continue;

src/Complete/Completer/Completer.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function __construct(
1818
$this->namespaceCompleter = $namespaceCompleter;
1919
$this->objectCompleter = $objectCompleter;
2020
}
21-
public function getEntries(Project $project, Context $context, Scope $scope){
21+
public function getEntries(Project $project, Context $context){
2222
if($context->isNamespace()){
2323
return $this->getAllNamespaces($project, $context);
2424
}
@@ -29,7 +29,7 @@ public function getEntries(Project $project, Context $context, Scope $scope){
2929
return $this->getAllInterfaces($project, $context);
3030
}
3131
elseif($context->isThis() || $context->isObject()){
32-
return $this->getObjectCompletion($project, $context, $scope);
32+
return $this->getObjectCompletion($project, $context);
3333
}
3434
return [];
3535
}
@@ -42,8 +42,8 @@ protected function getAllClasses(Project $project, Context $context){
4242
protected function getAllInterfaces(Project $project, Context $context){
4343
return $this->interfaceNameCompleter->getEntries($project, $context);
4444
}
45-
protected function getObjectCompletion(Project $project, Context $context, Scope $scope){
46-
return $this->objectCompleter->getEntries($project, $context, $scope);
45+
protected function getObjectCompletion(Project $project, Context $context){
46+
return $this->objectCompleter->getEntries($project, $context);
4747
}
4848

4949
private $classNameCompleter;

0 commit comments

Comments
 (0)