-
Notifications
You must be signed in to change notification settings - Fork 517
Expand file tree
/
Copy pathCSharpPattern.cs
More file actions
272 lines (247 loc) · 12.1 KB
/
CSharpPattern.cs
File metadata and controls
272 lines (247 loc) · 12.1 KB
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using OpenRewrite.Core;
using OpenRewrite.Java;
namespace OpenRewrite.CSharp.Template;
/// <summary>
/// A structural pattern for matching against C# AST nodes.
/// Patterns are created using C# string interpolation with <see cref="Capture{T}"/> placeholders.
/// </summary>
/// <example>
/// <code>
/// var expr = Capture.Of<Expression>("expr");
/// var pat = CSharpPattern.Expression($"Console.Write({expr})");
///
/// if (pat.Match(methodInvocation, cursor) is { } match)
/// {
/// var capturedExpr = match.Get(expr);
/// }
/// </code>
/// </example>
public sealed class CSharpPattern
{
private readonly string _code;
private readonly IReadOnlyDictionary<string, object> _captures;
private readonly IReadOnlyList<string> _usings;
private readonly IReadOnlyList<string> _context;
private readonly IReadOnlyDictionary<string, string> _dependencies;
private readonly ScaffoldKind? _scaffoldKind;
private J? _cachedTree;
private CSharpPattern(string code, Dictionary<string, object> captures,
IReadOnlyList<string>? usings, IReadOnlyList<string>? context,
IReadOnlyDictionary<string, string>? dependencies,
ScaffoldKind? scaffoldKind = null)
{
_code = code;
_captures = captures;
_usings = usings ?? [];
_context = context ?? [];
_dependencies = dependencies ?? new Dictionary<string, string>();
_scaffoldKind = scaffoldKind;
}
/// <summary>
/// Create a pattern with auto-detected scaffold kind.
/// Prefer <see cref="Expression"/>, <see cref="Statement"/>,
/// <see cref="ClassMember"/>, or <see cref="Attribute"/> for explicit scaffold control.
/// </summary>
[Obsolete("Use Expression(), Statement(), ClassMember(), or Attribute() for explicit scaffold control.")]
public static CSharpPattern Create(TemplateStringHandler handler,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(handler.GetCode(), handler.GetCaptures(), usings, context, dependencies);
}
/// <inheritdoc cref="Create(TemplateStringHandler, IReadOnlyList{string}?, IReadOnlyList{string}?, IReadOnlyDictionary{string, string}?)"/>
[Obsolete("Use Expression(), Statement(), ClassMember(), or Attribute() for explicit scaffold control.")]
public static CSharpPattern Create(string code,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(code, new Dictionary<string, object>(), usings, context, dependencies);
}
/// <summary>
/// Create a pattern that matches an expression.
/// </summary>
public static CSharpPattern Expression(TemplateStringHandler handler,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(handler.GetCode(), handler.GetCaptures(), usings, context, dependencies, ScaffoldKind.Expression);
}
/// <inheritdoc cref="Expression(TemplateStringHandler, IReadOnlyList{string}?, IReadOnlyList{string}?, IReadOnlyDictionary{string, string}?)"/>
public static CSharpPattern Expression(string code,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(code, new Dictionary<string, object>(), usings, context, dependencies, ScaffoldKind.Expression);
}
/// <summary>
/// Create a pattern that matches a statement.
/// </summary>
public static CSharpPattern Statement(TemplateStringHandler handler,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(handler.GetCode(), handler.GetCaptures(), usings, context, dependencies, ScaffoldKind.Statement);
}
/// <inheritdoc cref="Statement(TemplateStringHandler, IReadOnlyList{string}?, IReadOnlyList{string}?, IReadOnlyDictionary{string, string}?)"/>
public static CSharpPattern Statement(string code,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(code, new Dictionary<string, object>(), usings, context, dependencies, ScaffoldKind.Statement);
}
/// <summary>
/// Create a pattern that matches a class member (method, field, property, etc.).
/// </summary>
public static CSharpPattern ClassMember(TemplateStringHandler handler,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(handler.GetCode(), handler.GetCaptures(), usings, context, dependencies, ScaffoldKind.ClassMember);
}
/// <inheritdoc cref="ClassMember(TemplateStringHandler, IReadOnlyList{string}?, IReadOnlyList{string}?, IReadOnlyDictionary{string, string}?)"/>
public static CSharpPattern ClassMember(string code,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(code, new Dictionary<string, object>(), usings, context, dependencies, ScaffoldKind.ClassMember);
}
/// <summary>
/// Create a pattern that matches an attribute (C# <c>[Foo]</c>).
/// </summary>
public static CSharpPattern Attribute(TemplateStringHandler handler,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(handler.GetCode(), handler.GetCaptures(), usings, context, dependencies, ScaffoldKind.Attribute);
}
/// <inheritdoc cref="Attribute(TemplateStringHandler, IReadOnlyList{string}?, IReadOnlyList{string}?, IReadOnlyDictionary{string, string}?)"/>
public static CSharpPattern Attribute(string code,
IReadOnlyList<string>? usings = null, IReadOnlyList<string>? context = null,
IReadOnlyDictionary<string, string>? dependencies = null)
{
return new CSharpPattern(code, new Dictionary<string, object>(), usings, context, dependencies, ScaffoldKind.Attribute);
}
/// <summary>
/// Create a <see cref="Rewriter"/> that rewrites nodes matching this pattern
/// to the given template.
/// </summary>
public Rewriter RewriteTo(CSharpTemplate template) => new Rewriter(this, template);
/// <summary>
/// Get the parsed pattern tree (cached after first parse).
/// </summary>
public J GetTree()
{
return _cachedTree ??= TemplateEngine.Parse(_code, _captures, _usings, _context, _dependencies, _scaffoldKind);
}
/// <summary>
/// Match this pattern against an AST node.
/// Returns a <see cref="MatchResult"/> if matched, null otherwise.
/// </summary>
public MatchResult? Match(J tree, Cursor cursor)
{
var patternTree = GetTree();
// Fast reject: if the pattern root is not a capture placeholder and the
// candidate is a different node type, no match is possible — unless the
// comparator has a known cross-type equivalence (e.g. Binary ↔ IsPattern).
// This avoids allocating a PatternMatchingComparator for the common non-matching case.
if (patternTree.GetType() != tree.GetType()
&& !IsCapturePlaceholder(patternTree)
&& !PatternMatchingComparator.HasCrossTypeEquivalence(patternTree, tree))
return null;
var comparator = new PatternMatchingComparator(_captures);
var captured = comparator.Match(patternTree, tree, cursor);
return captured != null ? new MatchResult(captured) : null;
}
private bool IsCapturePlaceholder(J node)
{
return node is Identifier id
&& Placeholder.FromPlaceholder(id.SimpleName) is { } name
&& _captures.ContainsKey(name);
}
/// <summary>
/// Check if this pattern matches (without capturing).
/// </summary>
public bool Matches(J tree, Cursor cursor) => Match(tree, cursor) != null;
/// <summary>
/// If this pattern matches the given tree node, return the node with a
/// <see cref="SearchResult"/> marker added. Otherwise return the node unchanged.
/// This is the search-only equivalent of template apply — it marks found
/// syntax so that <c>/*~~>*/</c> appears in printed output.
/// </summary>
public T Find<T>(T tree, Cursor cursor, string? description = null) where T : J
{
return Find(tree, cursor, (T t, Cursor _, MatchResult _) => SearchResult.Found(t, description));
}
/// <summary>
/// If this pattern matches the given tree node, call <paramref name="annotator"/> with
/// the matched node, cursor, and match result, returning the annotated node.
/// If the pattern does not match, returns the node unchanged.
/// </summary>
/// <example>
/// <code>
/// return pat.Find(mi, Cursor, (node, _, _) => Markup.CreateWarn(node, "Avoid this API"));
/// </code>
/// </example>
public T Find<T>(T tree, Cursor cursor, Func<T, Cursor, MatchResult, T> annotator) where T : J
{
var match = Match(tree, cursor);
return match != null ? annotator(tree, cursor, match) : tree;
}
/// <summary>
/// Create a <see cref="CSharpVisitor{ExecutionContext}"/> that visits every node and
/// adds a <see cref="SearchResult"/> marker to matches. The pattern's fast-reject in
/// <see cref="Match"/> ensures only nodes whose type matches the pattern root are fully
/// compared, so iterating over all nodes is cheap.
/// </summary>
/// <example>
/// <code>
/// public override JavaVisitor<ExecutionContext> GetVisitor() =>
/// CSharpPattern.Expression("Console.WriteLine(\"hello\")")
/// .ToFindVisitor("found it");
/// </code>
/// </example>
public CSharpVisitor<Core.ExecutionContext> ToFindVisitor(string? description = null)
{
return ToFindVisitor((node, _, _) => SearchResult.Found(node, description));
}
/// <summary>
/// Create a <see cref="CSharpVisitor{ExecutionContext}"/> that visits every node and
/// calls <paramref name="annotator"/> on matches. Use this to produce visitors that mark
/// matches with custom markers (e.g. <see cref="Markup.CreateWarn{T}"/>).
/// </summary>
/// <example>
/// <code>
/// public override JavaVisitor<ExecutionContext> GetVisitor() =>
/// CSharpPattern.Expression($"new BinaryFormatter({args})")
/// .ToFindVisitor((node, _, _) => Markup.CreateWarn(node, "BinaryFormatter is obsolete"));
/// </code>
/// </example>
public CSharpVisitor<Core.ExecutionContext> ToFindVisitor(Func<J, Cursor, MatchResult, J> annotator)
{
return new FindVisitor(this, annotator);
}
private sealed class FindVisitor(CSharpPattern pattern, Func<J, Cursor, MatchResult, J> annotator)
: CSharpVisitor<Core.ExecutionContext>
{
public override J? PostVisit(J tree, Core.ExecutionContext ctx)
{
var match = pattern.Match(tree, Cursor);
return match != null ? annotator(tree, Cursor, match) : tree;
}
}
}