forked from PowerShell/PowerShellEditorServices
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathReferenceTable.cs
118 lines (95 loc) · 3.83 KB
/
ReferenceTable.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
namespace Microsoft.PowerShell.EditorServices.Services;
/// <summary>
/// Represents the symbols that are referenced and their locations within a single document.
/// </summary>
internal sealed class ReferenceTable
{
private readonly ScriptFile _parent;
private readonly ConcurrentDictionary<string, ConcurrentBag<IScriptExtent>> _symbolReferences = new(StringComparer.OrdinalIgnoreCase);
private bool _isInited;
public ReferenceTable(ScriptFile parent) => _parent = parent;
/// <summary>
/// Clears the reference table causing it to rescan the source AST when queried.
/// </summary>
public void TagAsChanged()
{
_symbolReferences.Clear();
_isInited = false;
}
// Prefer checking if the dictionary has contents to determine if initialized. The field
// `_isInited` is to guard against rescanning files with no command references, but will
// generally be less reliable of a check.
private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited;
internal bool TryGetReferences(string command, out ConcurrentBag<IScriptExtent>? references)
{
EnsureInitialized();
return _symbolReferences.TryGetValue(command, out references);
}
internal void EnsureInitialized()
{
if (IsInitialized)
{
return;
}
_parent.ScriptAst.Visit(new ReferenceVisitor(this));
}
private void AddReference(string symbol, IScriptExtent extent)
{
_symbolReferences.AddOrUpdate(
symbol,
_ => new ConcurrentBag<IScriptExtent> { extent },
(_, existing) =>
{
existing.Add(extent);
return existing;
});
}
private sealed class ReferenceVisitor : AstVisitor
{
private readonly ReferenceTable _references;
public ReferenceVisitor(ReferenceTable references) => _references = references;
public override AstVisitAction VisitCommand(CommandAst commandAst)
{
string? commandName = GetCommandName(commandAst);
if (string.IsNullOrEmpty(commandName))
{
return AstVisitAction.Continue;
}
_references.AddReference(
CommandHelpers.StripModuleQualification(commandName, out _),
commandAst.CommandElements[0].Extent);
return AstVisitAction.Continue;
static string? GetCommandName(CommandAst commandAst)
{
string commandName = commandAst.GetCommandName();
if (!string.IsNullOrEmpty(commandName))
{
return commandName;
}
if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst)
{
return null;
}
return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null;
}
}
public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
{
// TODO: Consider tracking unscoped variable references only when they declared within
// the same function definition.
_references.AddReference(
$"${variableExpressionAst.VariablePath.UserPath}",
variableExpressionAst.Extent);
return AstVisitAction.Continue;
}
}
}