Skip to content
Permalink
Browse files

Add autocompletion for some classes

Ref #1822
  • Loading branch information...
muglug committed Jun 21, 2019
1 parent c74e196 commit aa6677a177ce66b9cb7586dffa3db1b0a7e1a733
@@ -1124,7 +1124,7 @@ public function getReferenceAtPosition(string $file_path, Position $position)
}
/**
* @return array{0: string, 1: string}|null
* @return array{0: string, 1: '->'|'::'|'symbol', 2: array<string, string>}|null
*/
public function getCompletionDataAtPosition(string $file_path, Position $position)
{
@@ -1138,9 +1138,9 @@ public function getCompletionDataAtPosition(string $file_path, Position $positio
$offset = $position->toOffset($file_contents);
list(, $type_map) = $this->analyzer->getMapsForFile($file_path);
list($reference_map, $type_map, $alias_map) = $this->analyzer->getMapsForFile($file_path);
if (!$type_map) {
if (!$reference_map && !$type_map) {
return null;
}
@@ -1167,19 +1167,41 @@ public function getCompletionDataAtPosition(string $file_path, Position $positio
$gap = $candidate_gap;
$recent_type = $possible_type;
break;
if ($recent_type === 'mixed') {
return null;
}
return [$recent_type, $gap, []];
}
}
}
if (!$recent_type
|| $recent_type === 'mixed'
|| !$gap
) {
return null;
foreach ($reference_map as $start_pos => list($end_pos, $possible_reference)) {
if ($offset < $start_pos || $possible_reference[0] !== '*') {
continue;
}
if ($offset - $end_pos === 0) {
$recent_type = $possible_reference;
$found_aliases = [];
foreach ($alias_map as $aliases_start_pos => list($aliases_end_pos, $aliases)) {
if ($offset < $aliases_start_pos) {
continue;
}
if ($offset < $aliases_end_pos) {
$found_aliases = $aliases;
break;
}
}
return [$recent_type, 'symbol', $found_aliases];
}
}
return [$recent_type, $gap];
return null;
}
/**
@@ -1241,9 +1263,9 @@ public function getCompletionItemsForClassishThing(string $type_string, string $
foreach ($class_storage->class_constant_locations as $const_name => $_) {
$static_completion_items[] = new \LanguageServerProtocol\CompletionItem(
'const ' . $const_name,
$const_name,
\LanguageServerProtocol\CompletionItemKind::VARIABLE,
null,
'const ' . $const_name,
null,
null,
$const_name,
@@ -1269,6 +1291,44 @@ public function getCompletionItemsForClassishThing(string $type_string, string $
return $completion_items;
}
/**
* @param array<string, string> $aliases
* @return array<int, \LanguageServerProtocol\CompletionItem>
*/
public function getCompletionItemsForPartialSymbol(string $type_string, array $aliases) : array
{
$matching_classlike_names = $this->classlikes->getMatchingClassLikeNames($type_string);
$completion_items = [];
$alias_namespace = array_shift($aliases);
foreach ($matching_classlike_names as $fq_class_name_lc) {
try {
$storage = $this->classlike_storage_provider->get($fq_class_name_lc);
} catch (\Exception $e) {
continue;
}
$completion_items[] = new \LanguageServerProtocol\CompletionItem(
$storage->name,
\LanguageServerProtocol\CompletionItemKind::CLASS_,
null,
null,
null,
$storage->name,
Type::getStringFromFQCLN(
$storage->name,
$alias_namespace ? $alias_namespace : null,
$aliases,
null
)
);
}
return $completion_items;
}
private static function getPositionFromOffset(int $offset, string $file_contents) : Position
{
$file_contents = substr($file_contents, 0, $offset);
@@ -296,11 +296,13 @@ public function analyze(
}
}
if ($codebase->store_node_types && $fq_class_name) {
if ($codebase->store_node_types && $parent_fq_class_name) {
$codebase->analyzer->addNodeReference(
$this->getFilePath(),
$class->extends,
$parent_fq_class_name
$codebase->classlikes->classExists($parent_fq_class_name)
? $parent_fq_class_name
: '*' . implode('\\', $class->extends->parts)
);
}
@@ -331,6 +333,14 @@ public function analyze(
$this->source->getAliases()
);
$codebase->analyzer->addNodeReference(
$this->getFilePath(),
$interface_name,
$codebase->classlikes->interfaceExists($fq_interface_name)
? $fq_interface_name
: '*' . implode('\\', $interface_name->parts)
);
$interface_location = new CodeLocation($this, $interface_name);
if (self::checkFullyQualifiedClassLikeName(
@@ -340,7 +350,7 @@ public function analyze(
$this->getSuppressedIssues(),
false
) === false) {
return false;
continue;
}
if ($codebase->store_node_types && $fq_class_name) {
@@ -81,6 +81,15 @@ public function collectAnalyzableInformation()
}
}
$codebase->analyzer->addNodeAliases(
$this->getFilePath(),
$this->namespace,
array_merge(
[$this->namespace_name => $this->namespace_name],
$this->getAliasedClassesFlipped()
)
);
if ($leftover_stmts) {
$statements_analyzer = new StatementsAnalyzer($this);
$context = new Context();
@@ -87,7 +87,9 @@ public static function analyze(
$codebase->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$stmt->class,
$fq_class_name
$codebase->classlikes->classExists($fq_class_name)
? $fq_class_name
: '*' . implode('\\', $stmt->class->parts)
);
}
} elseif ($stmt->class instanceof PhpParser\Node\Stmt\Class_) {
@@ -34,25 +34,29 @@
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*
* @psalm-type WorkerData = array{
* issues: array<int, IssueData>,
* file_references_to_classes: array<string, array<string,bool>>,
* file_references_to_class_members: array<string, array<string,bool>>,
* file_references_to_missing_class_members: array<string, array<string,bool>>,
* mixed_counts: array<string, array{0: int, 1: int}>,
* mixed_member_names: array<string, array<string, bool>>,
* file_manipulations: array<string, FileManipulation[]>,
* method_references_to_class_members: array<string, array<string,bool>>,
* method_references_to_missing_class_members: array<string, array<string,bool>>,
* method_param_uses: array<string, array<int, array<string, bool>>>,
* analyzed_methods: array<string, array<string, int>>,
* file_maps: array<
* string,
* array{0: TaggedCodeType, 1: TaggedCodeType}
* >,
* class_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_method_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_property_locations: array<string, array<int, \Psalm\CodeLocation>>,
* possible_method_param_types: array<string, array<int, \Psalm\Type\Union>>
* issues: array<int, IssueData>,
* file_references_to_classes: array<string, array<string,bool>>,
* file_references_to_class_members: array<string, array<string,bool>>,
* file_references_to_missing_class_members: array<string, array<string,bool>>,
* mixed_counts: array<string, array{0: int, 1: int}>,
* mixed_member_names: array<string, array<string, bool>>,
* file_manipulations: array<string, FileManipulation[]>,
* method_references_to_class_members: array<string, array<string,bool>>,
* method_references_to_missing_class_members: array<string, array<string,bool>>,
* method_param_uses: array<string, array<int, array<string, bool>>>,
* analyzed_methods: array<string, array<string, int>>,
* file_maps: array<
* string,
* array{
* 0: TaggedCodeType,
* 1: TaggedCodeType,
* 2: array<int, array{0: int, 1: array<string, string>}>
* }
* >,
* class_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_method_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_property_locations: array<string, array<int, \Psalm\CodeLocation>>,
* possible_method_param_types: array<string, array<int, \Psalm\Type\Union>>
* }
*/
@@ -136,6 +140,11 @@ class Analyzer
*/
private $type_map = [];
/**
* @var array<string, array<int, array{0: int, 1: array<string, string>}>>
*/
private $alias_map = [];
/**
* @var array<string, array<int, \Psalm\Type\Union>>
*/
@@ -416,9 +425,10 @@ function () {
FileManipulationBuffer::add($file_path, $manipulations);
}
foreach ($pool_data['file_maps'] as $file_path => list($reference_map, $type_map)) {
foreach ($pool_data['file_maps'] as $file_path => list($reference_map, $type_map, $alias_map)) {
$this->reference_map[$file_path] = $reference_map;
$this->type_map[$file_path] = $type_map;
$this->alias_map[$file_path] = $alias_map;
}
}
@@ -502,9 +512,10 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer)
$this->existing_issues = $codebase->file_reference_provider->getExistingIssues();
$file_maps = $codebase->file_reference_provider->getFileMaps();
foreach ($file_maps as $file_path => list($reference_map, $type_map)) {
foreach ($file_maps as $file_path => list($reference_map, $type_map, $alias_map)) {
$this->reference_map[$file_path] = $reference_map;
$this->type_map[$file_path] = $type_map;
$this->alias_map[$file_path] = $alias_map;
}
}
@@ -861,6 +872,39 @@ public function shiftFileOffsets(array $diff_map)
}
}
}
foreach ($this->alias_map as $file_path => &$alias_map) {
if (!isset($this->analyzed_methods[$file_path])) {
unset($this->alias_map[$file_path]);
continue;
}
$file_diff_map = $diff_map[$file_path] ?? [];
if (!$file_diff_map) {
continue;
}
$first_diff_offset = $file_diff_map[0][0];
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
foreach ($alias_map as $alias_from => list($alias_to, $tag)) {
if ($alias_to < $first_diff_offset || $alias_from > $last_diff_offset) {
continue;
}
foreach ($file_diff_map as list($from, $to, $file_offset)) {
if ($alias_from >= $from && $alias_from <= $to) {
unset($alias_map[$alias_from]);
$alias_map[$alias_from += $file_offset] = [
$alias_to += $file_offset,
$tag
];
}
}
}
}
}
/**
@@ -984,6 +1028,22 @@ public function addNodeType(
];
}
/**
* @param array<string, string> $aliases
* @return void
*/
public function addNodeAliases(
string $file_path,
PhpParser\Node $node,
array $aliases,
PhpParser\Node $parent_node = null
) {
$this->alias_map[$file_path][(int)$node->getAttribute('startFilePos')] = [
($parent_node ? (int)$parent_node->getAttribute('endFilePos') : (int)$node->getAttribute('endFilePos')) + 1,
$aliases
];
}
/**
* @return void
*/
@@ -1248,35 +1308,51 @@ public function getAnalyzedMethods()
}
/**
* @return array<string, array{0: TaggedCodeType, 1: TaggedCodeType}>
* @return array<
* string,
* array{
* 0: TaggedCodeType,
* 1: TaggedCodeType,
* 2: array<int, array{0: int, 1: array<string, string>}>
* }
* >
*/
public function getFileMaps()
{
$file_maps = [];
foreach ($this->reference_map as $file_path => $reference_map) {
$file_maps[$file_path] = [$reference_map, []];
$file_maps[$file_path] = [$reference_map, [], []];
}
foreach ($this->type_map as $file_path => $type_map) {
if (isset($file_maps[$file_path])) {
$file_maps[$file_path][1] = $type_map;
} else {
$file_maps[$file_path] = [[], $type_map];
$file_maps[$file_path] = [[], $type_map, []];
}
}
foreach ($this->alias_map as $file_path => $alias_map) {
if (isset($file_maps[$file_path])) {
$file_maps[$file_path][2] = $alias_map;
} else {
$file_maps[$file_path] = [[], [], $alias_map];
}
}
return $file_maps;
}
/**
* @return array{0: array<int, array{0: int, 1: string}>, 1: array<int, array{0: int, 1: string}>}
* @return array{0: TaggedCodeType, 1: TaggedCodeType, 2: array<int, array{0: int, 1: array<string, string>}>}
*/
public function getMapsForFile(string $file_path)
{
return [
$this->reference_map[$file_path] ?? [],
$this->type_map[$file_path] ?? []
$this->type_map[$file_path] ?? [],
$this->alias_map[$file_path] ?? [],
];
}

0 comments on commit aa6677a

Please sign in to comment.
You can’t perform that action at this time.