-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathSqlScriptGeneratorVisitor.Comments.cs
More file actions
284 lines (249 loc) · 10.4 KB
/
SqlScriptGeneratorVisitor.Comments.cs
File metadata and controls
284 lines (249 loc) · 10.4 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
273
274
275
276
277
278
279
280
281
282
283
284
//------------------------------------------------------------------------------
// <copyright file="SqlScriptGeneratorVisitor.Comments.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator
{
internal abstract partial class SqlScriptGeneratorVisitor
{
#region Comment Tracking Fields
/// <summary>
/// Tracks the last token index processed for comment emission.
/// Used to find comments between visited fragments.
/// </summary>
private int _lastProcessedTokenIndex = -1;
/// <summary>
/// The current script's token stream, set when visiting begins.
/// </summary>
private IList<TSqlParserToken> _currentTokenStream;
/// <summary>
/// Tracks which comment tokens have already been emitted to avoid duplicates.
/// </summary>
private readonly HashSet<TSqlParserToken> _emittedComments = new HashSet<TSqlParserToken>();
/// <summary>
/// Tracks whether leading (file-level) comments have been emitted.
/// </summary>
private bool _leadingCommentsEmitted = false;
#endregion
#region Comment Preservation Methods
/// <summary>
/// Sets the token stream for comment tracking.
/// Call this before visiting the root node when PreserveComments is enabled.
/// </summary>
/// <param name="tokenStream">The token stream from the parsed script.</param>
protected void SetTokenStreamForComments(IList<TSqlParserToken> tokenStream)
{
_currentTokenStream = tokenStream;
_lastProcessedTokenIndex = -1;
_emittedComments.Clear();
_leadingCommentsEmitted = false;
}
/// <summary>
/// Emits comments that appear before the first fragment in the script (file-level leading comments).
/// Called once when generating the first fragment.
/// </summary>
/// <param name="fragment">The first fragment being generated.</param>
protected void EmitLeadingComments(TSqlFragment fragment)
{
if (!_options.PreserveComments || _currentTokenStream == null || fragment == null)
{
return;
}
if (fragment.FirstTokenIndex <= 0)
{
return;
}
for (int i = 0; i < fragment.FirstTokenIndex && i < _currentTokenStream.Count; i++)
{
var token = _currentTokenStream[i];
if (IsCommentToken(token) && !_emittedComments.Contains(token))
{
EmitCommentToken(token, isLeading: true);
_emittedComments.Add(token);
}
}
}
/// <summary>
/// Emits comments that appear in the gap between the last emitted token and the current fragment.
/// This captures comments embedded within sub-expressions.
/// </summary>
/// <param name="fragment">The fragment about to be generated.</param>
protected void EmitGapComments(TSqlFragment fragment)
{
if (!_options.PreserveComments || _currentTokenStream == null || fragment == null)
{
return;
}
int startIndex = _lastProcessedTokenIndex + 1;
int endIndex = fragment.FirstTokenIndex;
if (endIndex <= startIndex)
{
return;
}
for (int i = startIndex; i < endIndex && i < _currentTokenStream.Count; i++)
{
var token = _currentTokenStream[i];
if (IsCommentToken(token) && !_emittedComments.Contains(token))
{
EmitCommentToken(token, isLeading: true);
_emittedComments.Add(token);
_lastProcessedTokenIndex = i;
}
}
}
/// <summary>
/// Emits trailing comments that appear immediately after the fragment.
/// </summary>
/// <param name="fragment">The fragment that was just generated.</param>
protected void EmitTrailingComments(TSqlFragment fragment)
{
if (!_options.PreserveComments || _currentTokenStream == null || fragment == null)
{
return;
}
int lastTokenIndex = fragment.LastTokenIndex;
if (lastTokenIndex < 0 || lastTokenIndex >= _currentTokenStream.Count)
{
return;
}
// Scan for comments immediately following the fragment
for (int i = lastTokenIndex + 1; i < _currentTokenStream.Count; i++)
{
var token = _currentTokenStream[i];
if (IsCommentToken(token) && !_emittedComments.Contains(token))
{
EmitCommentToken(token, isLeading: false);
_emittedComments.Add(token);
_lastProcessedTokenIndex = i;
}
else if (token.TokenType != TSqlTokenType.WhiteSpace)
{
// Stop at next non-whitespace, non-comment token
break;
}
}
}
/// <summary>
/// Updates tracking after generating a fragment.
/// </summary>
/// <param name="fragment">The fragment that was just generated.</param>
protected void UpdateLastProcessedIndex(TSqlFragment fragment)
{
if (fragment != null && fragment.LastTokenIndex > _lastProcessedTokenIndex)
{
_lastProcessedTokenIndex = fragment.LastTokenIndex;
}
}
/// <summary>
/// Called from GenerateFragmentIfNotNull to handle comments before generating a fragment.
/// This is the key integration point that enables comments within sub-expressions.
/// </summary>
/// <param name="fragment">The fragment about to be generated.</param>
protected void HandleCommentsBeforeFragment(TSqlFragment fragment)
{
if (!_options.PreserveComments || _currentTokenStream == null || fragment == null)
{
return;
}
// Emit file-level leading comments once
if (!_leadingCommentsEmitted)
{
EmitLeadingComments(fragment);
_leadingCommentsEmitted = true;
}
// Emit any comments in the gap between last processed token and this fragment
EmitGapComments(fragment);
}
/// <summary>
/// Called from GenerateFragmentIfNotNull to handle comments after generating a fragment.
/// </summary>
/// <param name="fragment">The fragment that was just generated.</param>
protected void HandleCommentsAfterFragment(TSqlFragment fragment)
{
if (!_options.PreserveComments || _currentTokenStream == null || fragment == null)
{
return;
}
// Emit trailing comments and update tracking
EmitTrailingComments(fragment);
UpdateLastProcessedIndex(fragment);
}
/// <summary>
/// Emits a comment token to the output.
/// </summary>
/// <param name="token">The comment token.</param>
/// <param name="isLeading">True if this is a leading comment, false for trailing.</param>
private void EmitCommentToken(TSqlParserToken token, bool isLeading)
{
if (token == null)
{
return;
}
if (token.TokenType == TSqlTokenType.SingleLineComment)
{
if (!isLeading)
{
// Trailing: add space before comment
_writer.AddToken(ScriptGeneratorSupporter.CreateWhitespaceToken(1));
}
_writer.AddToken(new TSqlParserToken(TSqlTokenType.SingleLineComment, token.Text));
if (isLeading)
{
// After a leading comment, add newline
_writer.NewLine();
}
}
else if (token.TokenType == TSqlTokenType.MultilineComment)
{
if (!isLeading)
{
// Trailing: add space before comment
_writer.AddToken(ScriptGeneratorSupporter.CreateWhitespaceToken(1));
}
_writer.AddToken(new TSqlParserToken(TSqlTokenType.MultilineComment, token.Text));
if (isLeading)
{
// After a leading multi-line comment, add newline
_writer.NewLine();
}
}
}
/// <summary>
/// Emits any remaining comments at the end of the token stream.
/// Call this after visiting the root fragment to capture comments that appear
/// after the last statement (end-of-script comments).
/// </summary>
protected void EmitRemainingComments()
{
if (!_options.PreserveComments || _currentTokenStream == null)
{
return;
}
// Scan from the last processed token to the end of the token stream
for (int i = _lastProcessedTokenIndex + 1; i < _currentTokenStream.Count; i++)
{
var token = _currentTokenStream[i];
if (IsCommentToken(token) && !_emittedComments.Contains(token))
{
// End-of-script comments: add newline before, emit comment
_writer.NewLine();
_writer.AddToken(new TSqlParserToken(token.TokenType, token.Text));
_emittedComments.Add(token);
}
}
}
/// <summary>
/// Checks if a token is a comment token.
/// </summary>
private static bool IsCommentToken(TSqlParserToken token)
{
return token != null &&
(token.TokenType == TSqlTokenType.SingleLineComment ||
token.TokenType == TSqlTokenType.MultilineComment);
}
#endregion
}
}