Skip to content

Commit 18667d3

Browse files
committed
Fixes
1 parent c9f8136 commit 18667d3

File tree

8 files changed

+169
-116
lines changed

8 files changed

+169
-116
lines changed

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetPathPattern.cs

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
77

88
#if WASM_TASKS
99
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
10+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Can't use range syntax in full framework")]
1011
internal sealed class StaticWebAssetPathPattern : IEquatable<StaticWebAssetPathPattern>
1112
#else
1213
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
1314
public sealed class StaticWebAssetPathPattern : IEquatable<StaticWebAssetPathPattern>
1415
#endif
1516
{
17+
private const string FingerprintStart = "#[";
18+
private const string FingerprintEnd = "]";
19+
private const char FingerprintOptional = '?';
20+
private const char FingerprintPreferred = '!';
21+
private const char FingerprintValueSeparator = '=';
22+
private const char FingerprintParameterStart = '{';
23+
private const char FingerprintParameterEnd = '}';
24+
1625
public StaticWebAssetPathPattern(string path) : this(path.AsMemory()) { }
1726

1827
public StaticWebAssetPathPattern(ReadOnlyMemory<char> rawPathMemory) => RawPattern = rawPathMemory;
@@ -61,28 +70,27 @@ public StaticWebAssetPathPattern(List<StaticWebAssetPathSegment> segments)
6170
public static StaticWebAssetPathPattern Parse(ReadOnlyMemory<char> rawPathMemory, string assetIdentity = null)
6271
{
6372
var pattern = new StaticWebAssetPathPattern(rawPathMemory);
64-
var nextToken = MemoryExtensions.IndexOf(rawPathMemory.Span, "#[".AsSpan(), StringComparison.OrdinalIgnoreCase);
73+
var current = rawPathMemory;
74+
var nextToken = MemoryExtensions.IndexOf(current.Span, FingerprintStart.AsSpan(), StringComparison.OrdinalIgnoreCase);
6575
if (nextToken == -1)
6676
{
6777
var literalSegment = new StaticWebAssetPathSegment();
68-
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = rawPathMemory, IsLiteral = true });
78+
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = current, IsLiteral = true });
6979
pattern.Segments.Add(literalSegment);
7080
return pattern;
7181
}
7282

7383
if (nextToken > 0)
7484
{
7585
var literalSegment = new StaticWebAssetPathSegment();
76-
#if NET9_0_OR_GREATER
77-
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = rawPathMemory[..nextToken], IsLiteral = true });
78-
#else
79-
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = rawPathMemory.Slice(0, nextToken), IsLiteral = true });
80-
#endif
86+
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = current.Slice(0, nextToken), IsLiteral = true });
8187
pattern.Segments.Add(literalSegment);
8288
}
89+
8390
while (nextToken != -1)
8491
{
85-
var tokenEnd = MemoryExtensions.IndexOf(rawPathMemory.Slice(nextToken).Span, "]".AsSpan(), StringComparison.Ordinal);
92+
current = current.Slice(nextToken);
93+
var tokenEnd = MemoryExtensions.IndexOf(current.Span, FingerprintEnd.AsSpan(), StringComparison.Ordinal);
8694
if (tokenEnd == -1)
8795
{
8896
if (assetIdentity != null)
@@ -96,31 +104,36 @@ public static StaticWebAssetPathPattern Parse(ReadOnlyMemory<char> rawPathMemory
96104
}
97105
}
98106

99-
var tokenExpression = rawPathMemory.Slice(nextToken + 2, tokenEnd - nextToken - 2);
107+
var tokenExpression = current.Slice(2, tokenEnd - 2);
100108

101109
var token = new StaticWebAssetPathSegment();
102110
AddTokenSegmentParts(tokenExpression, token);
103111
pattern.Segments.Add(token);
104112

105113
// Check if the segment is optional (ends with ? or !)
106-
if (tokenEnd < rawPathMemory.Length - 1 && (rawPathMemory.Span[tokenEnd + 1] == '?' || rawPathMemory.Span[tokenEnd + 1] == '!'))
114+
if (tokenEnd < current.Length - 1 &&
115+
(current.Span[tokenEnd + 1] == FingerprintOptional || current.Span[tokenEnd + 1] == FingerprintPreferred))
107116
{
108117
token.IsOptional = true;
109-
if (rawPathMemory.Span[tokenEnd + 1] == '!')
118+
if (current.Span[tokenEnd + 1] == FingerprintPreferred)
110119
{
111120
token.IsPreferred = true;
112121
}
113122
tokenEnd++;
114123
}
124+
current = current.Slice(tokenEnd + 1);
125+
nextToken = MemoryExtensions.IndexOf(current.Span, FingerprintStart.AsSpan(), StringComparison.OrdinalIgnoreCase);
115126

116-
nextToken = MemoryExtensions.IndexOf(rawPathMemory.Slice(tokenEnd).Span, "#[".AsSpan(), StringComparison.OrdinalIgnoreCase);
117-
118-
// Add a literal segment if there is more content after the token and before the next one
119-
if ((nextToken != -1 && nextToken > tokenEnd + 1) || (nextToken == -1 && tokenEnd < rawPathMemory.Length - 1))
127+
if (nextToken == -1 && current.Length > 0)
120128
{
121-
var literalEnd = nextToken == -1 ? rawPathMemory.Length : nextToken;
122129
var literalSegment = new StaticWebAssetPathSegment();
123-
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = rawPathMemory.Slice(tokenEnd + 1, literalEnd - tokenEnd - 1), IsLiteral = true });
130+
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = current, IsLiteral = true });
131+
pattern.Segments.Add(literalSegment);
132+
}
133+
else if (nextToken > 0)
134+
{
135+
var literalSegment = new StaticWebAssetPathSegment();
136+
literalSegment.Parts.Add(new StaticWebAssetSegmentPart { Name = current.Slice(0, nextToken), IsLiteral = true });
124137
pattern.Segments.Add(literalSegment);
125138
}
126139
}
@@ -170,14 +183,15 @@ public static StaticWebAssetPathPattern Parse(string rawPath, string assetIdenti
170183
var missingValue = "";
171184
foreach (var tokenName in tokenNames)
172185
{
173-
if (!tokens.TryGetValue(staticWebAsset, tokenName.ToString(), out var tokenValue) || string.IsNullOrEmpty(tokenValue))
186+
var tokenNameString = tokenName.ToString();
187+
if (!tokens.TryGetValue(staticWebAsset, tokenNameString, out var tokenValue) || string.IsNullOrEmpty(tokenValue))
174188
{
175189
foundAllValues = false;
176-
missingValue = tokenName.ToString();
190+
missingValue = tokenNameString;
177191
break;
178192
}
179193

180-
dictionary[tokenName.ToString()] = tokenValue;
194+
dictionary[tokenNameString] = tokenValue;
181195
}
182196

183197
if (!foundAllValues && !segment.IsOptional)
@@ -368,46 +382,50 @@ internal void EmbedTokens(StaticWebAsset staticWebAsset, StaticWebAssetTokenReso
368382
// The value within the {} represents token variables.
369383
private static void AddTokenSegmentParts(ReadOnlyMemory<char> tokenExpression, StaticWebAssetPathSegment token)
370384
{
371-
var nextToken = MemoryExtensions.IndexOf(tokenExpression.Span, "{".AsSpan(), StringComparison.Ordinal);
385+
var current = tokenExpression;
386+
var nextToken = MemoryExtensions.IndexOf(current.Span, FingerprintParameterStart);
372387
if (nextToken is not (-1) and > 0)
373388
{
374-
#if NET9_0_OR_GREATER
375-
var literalPart = new StaticWebAssetSegmentPart { Name = tokenExpression[..nextToken], IsLiteral = true };
376-
#else
377-
var literalPart = new StaticWebAssetSegmentPart { Name = tokenExpression.Slice(0, nextToken), IsLiteral = true };
378-
#endif
389+
var literalPart = new StaticWebAssetSegmentPart { Name = current.Slice(0, nextToken), IsLiteral = true };
379390
token.Parts.Add(literalPart);
380391
}
392+
381393
while (nextToken != -1)
382394
{
383-
var tokenEnd = MemoryExtensions.IndexOf(tokenExpression.Slice(nextToken).Span, "}".AsSpan(), StringComparison.Ordinal);
395+
current = current.Slice(nextToken);
396+
var tokenEnd = MemoryExtensions.IndexOf(current.Span, FingerprintParameterEnd);
384397
if (tokenEnd == -1)
385398
{
386399
throw new InvalidOperationException($"Invalid token expression '{tokenExpression}'. Missing '}}' token.");
387400
}
388401

389-
var embeddedValue = MemoryExtensions.IndexOf(tokenExpression.Slice(nextToken).Span, "=".AsSpan(), StringComparison.Ordinal);
402+
var embeddedValue = MemoryExtensions.IndexOf(current.Span, FingerprintValueSeparator);
390403
if (embeddedValue != -1)
391404
{
392405
var tokenPart = new StaticWebAssetSegmentPart
393406
{
394-
Name = tokenExpression.Slice(nextToken + 1, embeddedValue - nextToken - 1),
407+
Name = current.Slice(1, embeddedValue - 1),
395408
IsLiteral = false,
396-
Value = tokenExpression.Slice(embeddedValue + 1, tokenEnd - embeddedValue - 1)
409+
Value = current.Slice(embeddedValue + 1, tokenEnd - embeddedValue - 1)
397410
};
398411
token.Parts.Add(tokenPart);
399412
}
400413
else
401414
{
402-
var tokenPart = new StaticWebAssetSegmentPart { Name = tokenExpression.Slice(nextToken + 1, tokenEnd - nextToken - 1), IsLiteral = false };
415+
var tokenPart = new StaticWebAssetSegmentPart { Name = current.Slice(1, tokenEnd - 1), IsLiteral = false };
403416
token.Parts.Add(tokenPart);
404417
}
405418

406-
nextToken = MemoryExtensions.IndexOf(tokenExpression.Slice(tokenEnd).Span, "}".AsSpan(), StringComparison.Ordinal);
407-
if ((nextToken != -1 && nextToken > tokenEnd + 1) || (nextToken == -1 && tokenEnd < tokenExpression.Length - 1))
419+
current = current.Slice(tokenEnd + 1);
420+
nextToken = MemoryExtensions.IndexOf(current.Span, FingerprintParameterStart);
421+
if (nextToken == -1 && current.Length > 0)
422+
{
423+
var literalPart = new StaticWebAssetSegmentPart { Name = current, IsLiteral = true };
424+
token.Parts.Add(literalPart);
425+
}
426+
else if (nextToken > 0)
408427
{
409-
var literalEnd = nextToken == -1 ? tokenExpression.Length : nextToken;
410-
var literalPart = new StaticWebAssetSegmentPart { Name = tokenExpression.Slice(tokenEnd + 1, literalEnd - tokenEnd - 1), IsLiteral = true };
428+
var literalPart = new StaticWebAssetSegmentPart { Name = current.Slice(0, nextToken), IsLiteral = true };
411429
token.Parts.Add(literalPart);
412430
}
413431
}

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetPathSegment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ internal ICollection<ReadOnlyMemory<char>> GetTokenNames()
5757
var result = new HashSet<ReadOnlyMemory<char>>();
5858
foreach (var part in Parts)
5959
{
60-
if (!part.IsLiteral && part.Value.Length > 0)
60+
if (!part.IsLiteral && part.Name.Length > 0)
6161
{
6262
result.Add(part.Name);
6363
}

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetSegmentPart.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
57

8+
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
69
public class StaticWebAssetSegmentPart : IEquatable<StaticWebAssetSegmentPart>
710
{
811
public ReadOnlyMemory<char> Name { get; set; }
@@ -33,4 +36,6 @@ public override int GetHashCode()
3336

3437
public static bool operator ==(StaticWebAssetSegmentPart left, StaticWebAssetSegmentPart right) => EqualityComparer<StaticWebAssetSegmentPart>.Default.Equals(left, right);
3538
public static bool operator !=(StaticWebAssetSegmentPart left, StaticWebAssetSegmentPart right) => !(left == right);
39+
40+
private string GetDebuggerDisplay() => IsLiteral ? Value.ToString() : $"{{{Name}}}";
3641
}

src/StaticWebAssetsSdk/Tasks/DefineStaticWebAssets.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public override bool Execute()
8989
null;
9090

9191
var assetsByRelativePath = new Dictionary<string, List<ITaskItem>>();
92-
var fingerprintPatternMatcher = new FingerprintPatternMatcher(Log, FingerprintPatterns);
92+
var fingerprintPatternMatcher = new FingerprintPatternMatcher(Log, FingerprintCandidates ? (FingerprintPatterns ?? []) : []);
9393
var matchContext = StaticWebAssetGlobMatcher.CreateMatchContext();
9494
for (var i = 0; i < CandidateAssets.Length; i++)
9595
{
@@ -234,7 +234,7 @@ public override bool Execute()
234234

235235
if (FingerprintCandidates)
236236
{
237-
matchContext.SetPathAndReinitialize(candidate.ItemSpec);
237+
matchContext.SetPathAndReinitialize(relativePathCandidate);
238238
relativePathCandidate = StaticWebAsset.Normalize(fingerprintPatternMatcher.AppendFingerprintPattern(matchContext, identity));
239239
}
240240

src/StaticWebAssetsSdk/Tasks/FingerprintPatternMatcher.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public string AppendFingerprintPattern(StaticWebAssetGlobMatcher.MatchContext co
4545

4646
var (directoryName, fileName, fileNamePrefix, extension) =
4747
#if NET9_0_OR_GREATER
48-
ComputeFingerprintFragments(relativePathCandidateMemory);
48+
ComputeFingerprintFragments(relativePathCandidateMemory);
4949
#else
5050
ComputeFingerprintFragments(context.PathString);
5151
#endif
@@ -55,7 +55,7 @@ public string AppendFingerprintPattern(StaticWebAssetGlobMatcher.MatchContext co
5555
if (!matchResult.IsMatch)
5656
{
5757
#if NET9_0_OR_GREATER
58-
var result = Path.Combine(directoryName.ToString(), $"{fileNamePrefix}{DefaultFingerprintExpression}{extension}");
58+
var result = Path.Combine(directoryName.ToString(), $"{fileNamePrefix}{DefaultFingerprintExpression}{extension}");
5959
#else
6060
var result = Path.Combine(directoryName, $"{fileNamePrefix}{DefaultFingerprintExpression}{extension}");
6161
#endif
@@ -71,8 +71,8 @@ public string AppendFingerprintPattern(StaticWebAssetGlobMatcher.MatchContext co
7171
}
7272
else
7373
{
74-
var stem = GetMatchStem(relativePathCandidateMemory, matchResult.Pattern);
75-
var matchExtension = GetMatchExtension(relativePathCandidateMemory, stem);
74+
var stem = GetMatchStem(fileName, matchResult.Pattern.AsMemory().Slice(2));
75+
var matchExtension = GetMatchExtension(fileName, stem);
7676

7777
var simpleExtensionResult = Path.Combine(directoryName.ToString(), $"{stem}{expression}{matchExtension}");
7878
_log.LogMessage(MessageImportance.Low, "Fingerprinting asset '{0}' as '{1}'", relativePathCandidateMemory, simpleExtensionResult);
@@ -102,11 +102,17 @@ static bool AlreadyContainsFingerprint(ReadOnlyMemory<char> relativePathCandidat
102102
return false;
103103
}
104104

105+
#if NET9_0_OR_GREATER
106+
static ReadOnlySpan<char> GetMatchExtension(ReadOnlySpan<char> relativePathCandidateMemory, ReadOnlySpan<char> stem) =>
107+
relativePathCandidateMemory.Slice(stem.Length);
108+
static ReadOnlySpan<char> GetMatchStem(ReadOnlySpan<char> relativePathCandidateMemory, ReadOnlyMemory<char> pattern) =>
109+
relativePathCandidateMemory.Slice(0, relativePathCandidateMemory.Length - pattern.Length - 1);
110+
#else
105111
static ReadOnlyMemory<char> GetMatchExtension(ReadOnlyMemory<char> relativePathCandidateMemory, ReadOnlyMemory<char> stem) =>
106112
relativePathCandidateMemory.Slice(stem.Length);
107-
108-
static ReadOnlyMemory<char> GetMatchStem(ReadOnlyMemory<char> relativePathCandidateMemory, string pattern) =>
113+
static ReadOnlyMemory<char> GetMatchStem(ReadOnlyMemory<char> relativePathCandidateMemory, ReadOnlyMemory<char> pattern) =>
109114
relativePathCandidateMemory.Slice(0, relativePathCandidateMemory.Length - pattern.Length - 1);
115+
#endif
110116
}
111117

112118
#if NET9_0_OR_GREATER
@@ -121,13 +127,13 @@ private static FingerprintFragments ComputeFingerprintFragments(
121127
return new(directoryName, fileName, stem, extension);
122128
}
123129
#else
124-
private static (string directoryName, string fileName, string fileNamePrefix, string extension) ComputeFingerprintFragments(
130+
private static (string directoryName, ReadOnlyMemory<char> fileName, ReadOnlyMemory<char> fileNamePrefix, ReadOnlyMemory<char> extension) ComputeFingerprintFragments(
125131
string relativePathCandidate)
126132
{
127-
var fileName = Path.GetFileName(relativePathCandidate);
133+
var fileName = Path.GetFileName(relativePathCandidate).AsMemory();
128134
var directoryName = Path.GetDirectoryName(relativePathCandidate);
129-
var stem = Path.GetFileNameWithoutExtension(relativePathCandidate);
130-
var extension = Path.GetExtension(relativePathCandidate);
135+
var stem = Path.GetFileNameWithoutExtension(relativePathCandidate).AsMemory();
136+
var extension = Path.GetExtension(relativePathCandidate).AsMemory();
131137

132138
return (directoryName, fileName, stem, extension);
133139
}

src/StaticWebAssetsSdk/Tasks/Utils/Globbing/PathTokenizer.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
77

8+
#if !NET9_0_OR_GREATER
89
public ref struct PathTokenizer(ReadOnlySpan<char> path)
910
{
1011
private readonly ReadOnlySpan<char> _path = path;
@@ -70,3 +71,48 @@ private SegmentCollection(ReadOnlySpan<char> path, List<Segment> segments, int i
7071
internal SegmentCollection Slice(int segmentIndex) => new (_path, segments, segmentIndex);
7172
}
7273
}
74+
#else
75+
public ref struct PathTokenizer(ReadOnlySpan<char> path)
76+
{
77+
private readonly ReadOnlySpan<char> _path = path;
78+
79+
public struct Segment(int start, int length)
80+
{
81+
public int Start { get; set; } = start;
82+
public int Length { get; set; } = length;
83+
}
84+
85+
internal SegmentCollection Fill(List<Segment> segments)
86+
{
87+
foreach (var range in MemoryExtensions.SplitAny(_path, OSPath.DirectoryPathSeparators.Span))
88+
{
89+
var length = range.End.Value - range.Start.Value;
90+
if (length > 0 &&
91+
!_path.Slice(range.Start.Value, length).Equals(".".AsSpan(), StringComparison.Ordinal) &&
92+
!_path.Slice(range.Start.Value, length).Equals("..".AsSpan(), StringComparison.Ordinal))
93+
{
94+
segments.Add(new(range.Start.Value, length));
95+
}
96+
}
97+
98+
return new SegmentCollection(_path, segments);
99+
}
100+
101+
public readonly ref struct SegmentCollection(ReadOnlySpan<char> path, List<Segment> segments)
102+
{
103+
private readonly ReadOnlySpan<char> _path = path;
104+
private readonly int _index = 0;
105+
106+
private SegmentCollection(ReadOnlySpan<char> path, List<Segment> segments, int index) : this(path, segments) =>
107+
_index = index;
108+
109+
public int Count => segments.Count - _index;
110+
111+
public ReadOnlySpan<char> this[int index] => _path.Slice(segments[index + _index].Start, segments[index + _index].Length);
112+
113+
public ReadOnlyMemory<char> this[ReadOnlyMemory<char> path, int index] => path.Slice(segments[index + _index].Start, segments[index + _index].Length);
114+
115+
internal SegmentCollection Slice(int segmentIndex) => new(_path, segments, segmentIndex);
116+
}
117+
}
118+
#endif

0 commit comments

Comments
 (0)