This repository was archived by the owner on Nov 15, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 243
Expand file tree
/
Copy pathCodeCoverageStringTextSource.cs
More file actions
275 lines (232 loc) · 9.03 KB
/
CodeCoverageStringTextSource.cs
File metadata and controls
275 lines (232 loc) · 9.03 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
// Copyright (c) https://github.com/ddur
// This code is distributed under MIT license
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenCover.Framework.Model;
namespace OpenCover.Framework.Utility
{
/// <summary>
/// FileType enum
/// </summary>
public enum FileType : byte {
/// <summary>
/// Unsupported file extension
/// </summary>
Unsupported,
/// <summary>
/// File extension is ".cs"
/// </summary>
CSharp
}
/// <summary>StringTextSource (ReadOnly)
/// <remarks>Line and column counting starts at 1.</remarks>
/// </summary>
public class CodeCoverageStringTextSource
{
/// <summary>
/// File Type guessed by file-name extension
/// </summary>
public FileType FileType { get { return _fileType; } }
private readonly FileType _fileType = FileType.Unsupported;
/// <summary>
/// Path to source file
/// </summary>
public string FilePath { get { return _filePath; } }
private readonly string _filePath = string.Empty;
/// <summary>
/// Source file found or not
/// </summary>
public bool FileFound { get { return _fileFound; } }
private readonly bool _fileFound;
/// <summary>
/// Last write DateTime
/// </summary>
public DateTime FileTime { get { return _fileTime; } }
private readonly DateTime _fileTime = DateTime.MinValue;
private readonly string _textSource;
private struct LineInfo {
public int Offset;
public int Length;
}
private readonly LineInfo[] _lines = new LineInfo[0];
/// <summary>
/// Constructor
/// </summary>
/// <param name="source"></param>
/// <param name="filePath"></param>
public CodeCoverageStringTextSource(string source, string filePath)
{
_fileFound = source != null;
if (!string.IsNullOrWhiteSpace (filePath)) {
_filePath = filePath;
if (_filePath.IndexOfAny(Path.GetInvalidPathChars()) < 0
&& Path.GetExtension(_filePath).ToLowerInvariant() == ".cs" ) {
_fileType = FileType.CSharp;
}
if (_fileFound) {
try {
_fileTime = System.IO.File.GetLastWriteTime (this._filePath);
} catch (Exception e) {
e.InformUser();
}
}
}
_textSource = string.IsNullOrEmpty(source) ? string.Empty : source;
if (_textSource != string.Empty) {
_lines = InitLines ();
}
}
private LineInfo[] InitLines ()
{
int offset = 0;
int counter = 0;
var lineInfoList = new List<LineInfo>();
foreach (var ch in _textSource) {
if (NextChar(ch)) { // newLine detected - add line
lineInfoList.Add(new LineInfo { Offset = offset, Length = counter - offset });
offset = counter;
}
++counter;
}
// Add last line
lineInfoList.Add(new LineInfo { Offset = offset, Length = counter - offset });
return lineInfoList.ToArray();
}
private const ushort carriageReturn = 0xD;
private const ushort lineFeed = 0xA;
private bool cr = false;
private bool lf = false;
private bool NextChar(ushort ch)
{
switch (ch) {
case carriageReturn:
if (lf || cr) {
lf = false; // cr after cr|lf
return true;
}
cr = true; // cr found
break;
case lineFeed:
if (lf) { // lf after lf
return true;
}
lf = true; // lf found
break;
default:
if (cr || lf) { // any non-line-end char after any line-end
cr = false;
lf = false;
return true;
}
break;
}
return false;
}
/// <summary>Return text/source using SequencePoint line/col info
/// </summary>
/// <param name="sp"></param>
/// <returns></returns>
public string GetText(SequencePoint sp) {
return GetText(sp.StartLine, sp.StartColumn, sp.EndLine, sp.EndColumn );
}
/// <summary>Return text at Line/Column/EndLine/EndColumn position
/// <remarks>Line and Column counting starts at 1.</remarks>
/// </summary>
/// <param name="startLine"></param>
/// <param name="startColumn"></param>
/// <param name="endLine"></param>
/// <param name="endColumn"></param>
/// <returns></returns>
public string GetText(int startLine, int startColumn, int endLine, int endColumn) {
var text = new StringBuilder();
string line;
bool argOutOfRange;
if (startLine==endLine) {
#region One-Line request
line = GetLine(startLine);
argOutOfRange = startColumn > endColumn || startColumn > line.Length;
if (!argOutOfRange)
{
var actualStartColumn = (startColumn < 1) ? 1 : startColumn;
var actualEndColumn = (endColumn > line.Length + 1) ? line.Length + 1 : endColumn;
text.Append(line.Substring(actualStartColumn - 1, actualEndColumn - actualStartColumn));
}
#endregion
} else if (startLine<endLine) {
#region Multi-line request
#region First line
line = GetLine(startLine);
argOutOfRange = startColumn > line.Length;
if (!argOutOfRange) {
var actualStartColumn = (startColumn < 1) ? 1 : startColumn;
text.Append(line.Substring(actualStartColumn - 1));
}
#endregion
#region More than two lines
for ( int lineIndex = startLine + 1; lineIndex < endLine; lineIndex++ ) {
text.Append ( GetLine ( lineIndex ) );
}
#endregion
#region Last line
line = GetLine(endLine);
argOutOfRange = endColumn < 1;
if (!argOutOfRange) {
var actualEndColumn = (endColumn > line.Length + 1) ? line.Length + 1 : endColumn;
text.Append(line.Substring(0, actualEndColumn - 1));
}
#endregion
#endregion
}
return text.ToString();
}
/// <summary>
/// Return number of lines in source
/// </summary>
public int LinesCount {
get {
return _lines.Length;
}
}
/// <summary>Return SequencePoint enumerated line
/// </summary>
/// <param name="lineNo"></param>
/// <returns></returns>
public string GetLine ( int lineNo ) {
string retString = string.Empty;
if ( lineNo > 0 && lineNo <= _lines.Length ) {
LineInfo lineInfo = _lines[lineNo-1];
retString = _textSource.Substring(lineInfo.Offset, lineInfo.Length);
}
return retString;
}
/// <summary>
/// True if referenceTime != 0 and FileTime > referenceTime
/// </summary>
/// <param name="referenceTime"></param>
/// <returns></returns>
public bool IsChanged (DateTime referenceTime) {
return referenceTime != DateTime.MinValue && _fileTime > referenceTime;
}
/// <summary>
/// Get line-parsed source from file name
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static CodeCoverageStringTextSource GetSource(string filePath) {
var retSource = new CodeCoverageStringTextSource (null, filePath); // null indicates source-file not found
try {
using (Stream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var reader = new StreamReader (stream, Encoding.Default, true)) {
stream.Position = 0;
retSource = new CodeCoverageStringTextSource(reader.ReadToEnd(), filePath);
}
} catch (Exception e) {
// Source is optional (for excess-branch removal), application can continue without it
e.InformUser(); // Do not throw ExitApplicationWithoutReportingException
}
return retSource;
}
}
}