You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR adds proper placeholder escape sequence support to the manifest processing logic, allowing for the escaping of curly braces via doubling e.g. {{ or }}. The lack of support in the previous implementation was quite noticeable in the case of .NET format string placeholders e.g. {0}, which triggered unhandled exceptions during generation.
Care was taken to ensure backwards compatibility with the unresolved placeholder behaviors of the previous implementation, while also bringing the implementation closer to parity with Microsoft's. Several unit tests were added to cover all known edge cases in reference tokenization and unescaping.
The tokenizer and unescape logic may not handle all edge cases properly. For example, nested placeholders or malformed escape sequences could cause unexpected behavior.
publicstaticList<JsonInterpolationToken>Tokenize(ReadOnlySpan<char>input){varstate=TokenizerState.InText;varcurrentTokenIndex=0;// Estimate the number of tokens.vartokens=newList<JsonInterpolationToken>(input.Length/2);voidTryAddToken(refReadOnlySpan<char>input,JsonInterpolationTokenTypetokenType,intlength,intnextTokenIndex){if(length!=0||tokenType!=JsonInterpolationTokenType.Text){tokens.Add(newJsonInterpolationToken(tokenType,input.Slice(currentTokenIndex,length).ToString()));}currentTokenIndex=nextTokenIndex;}for(vari=0;i<input.Length;i++){varcurrentChar=input[i];switch(state){caseTokenizerState.InText:if(currentChar=='{'){// We are in a potential placeholder token start.state=TokenizerState.InPlaceholderStart;}break;caseTokenizerState.InPlaceholderStart:if(currentChar=='{'){// This curly brace is escaped, we're still in text.state=TokenizerState.InText;}else{// We are in a placeholder token, slice the previous text.TryAddToken(refinput,JsonInterpolationTokenType.Text,i-currentTokenIndex-1,i);// Advance to toke in placeholder token state.state=TokenizerState.InPlaceholder;}break;caseTokenizerState.InPlaceholder:// We are going for parity with the regular expression used in the// previous implementation: ([\w\.-]+)if(currentCharis(>='a' and <='z') or
(>='A' and <='Z') or
(>='0' and <='9') or
'.' or
'-'){// We are still in the placeholder token.continue;}elseif(currentChar=='}'){// Our placeholder token is complete.TryAddToken(refinput,JsonInterpolationTokenType.Placeholder,i-currentTokenIndex,i+1);// The next token is probably text.state=TokenizerState.InText;}else{// Our placeholder token has unsupported characters. As per// the original implementation, we are going to treat the// current lexme as text.state=TokenizerState.InText;}break;default:thrownewNotSupportedException();}}if(currentTokenIndex!=input.Length-1){// There is a dangling token. To ensure parity with the original// implementation, we're going to treat it as text.if(state==TokenizerState.InPlaceholder){// We were actually in a placeholder token, so we need to move// back a single character to ensure we catch the opening brace.currentTokenIndex--;}TryAddToken(refinput,JsonInterpolationTokenType.Text,input.Length-currentTokenIndex,0);}returntokens;}
The string builder allocation and multiple string operations in the placeholder replacement logic could be optimized for better performance with large JSON documents.
✅ Add null parameter validationSuggestion Impact:Added null checks for both Tokenize and Unescape methods to return empty results instead of throwing exceptions
code diff:
- public static List<JsonInterpolationToken> Tokenize(string input) => Tokenize(input.AsSpan());+ public static List<JsonInterpolationToken> Tokenize(string input) =>+ input is null ? [] : Tokenize(input.AsSpan());
public static List<JsonInterpolationToken> Tokenize(ReadOnlySpan<char> input)
{
@@ -188,6 +189,11 @@
public static string Unescape(string value)
{
+ if (value is null)+ {+ return string.Empty;+ }
Add null checks for input parameters in the Tokenize and Unescape methods to prevent potential NullReferenceExceptions.
-public static List<JsonInterpolationToken> Tokenize(string input) => Tokenize(input.AsSpan());+public static List<JsonInterpolationToken> Tokenize(string input) => + input is null ? new List<JsonInterpolationToken>() : Tokenize(input.AsSpan());-public static string Unescape(string value)+public static string Unescape(string value) => + value is null ? string.Empty : UnescapeImpl(value);
[Suggestion has been applied]
Suggestion importance[1-10]: 7
__
Why: Adding null checks for input parameters is important for preventing NullReferenceExceptions and making the code more robust. This is a defensive programming practice that improves code reliability.
Medium
Add thread safety protection
Add thread safety protection when processing JsonArray and JsonObject elements since ToArray() creates a copy but subsequent modifications could cause race conditions.
-foreach (var kvp in jsonObject.ToArray())+lock(jsonObject)
{
- UnescapeJsonExpression(kvp.Value);+ foreach (var kvp in jsonObject.ToArray())+ {+ UnescapeJsonExpression(kvp.Value);+ }
}
Apply this suggestion
Suggestion importance[1-10]: 3
__
Why: While thread safety is generally good practice, the context doesn't suggest this code needs to be thread-safe. The lock would add unnecessary overhead since JsonNode operations are typically not designed for concurrent modifications.
Hi John
Thanks for this looks good - Thanks
Any chance you can rebase on the latest .net 9 update thats in main, and resolve the small conflict?
I'll get it merged in and released in the next day
Hi John Thanks for this looks good - Thanks Any chance you can rebase on the latest .net 9 update thats in main, and resolve the small conflict? I'll get it merged in and released in the next day
Sure, no worries. I should be able to do it today or tomorrow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds proper placeholder escape sequence support to the manifest processing logic, allowing for the escaping of curly braces via doubling e.g.
{{or}}. The lack of support in the previous implementation was quite noticeable in the case of .NET format string placeholders e.g.{0}, which triggered unhandled exceptions during generation.While the Microsoft documentation makes no mention of this form of escaping, this sequence is supported by Microsoft in the Azure Developer CLI (azd).
Care was taken to ensure backwards compatibility with the unresolved placeholder behaviors of the previous implementation, while also bringing the implementation closer to parity with Microsoft's. Several unit tests were added to cover all known edge cases in reference tokenization and unescaping.