Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Parsing f-strings #685

Merged
merged 121 commits into from
Mar 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
59ba148
Added test file
brianbok Feb 7, 2019
f9a5a33
FString Expression
brianbok Feb 8, 2019
1c5c342
First test passing
brianbok Feb 13, 2019
5daa9ff
WIP Parser
brianbok Feb 13, 2019
14a9322
More tests
brianbok Feb 13, 2019
67eff3a
Escaping braces
brianbok Feb 13, 2019
60632ca
Merge branch 'master' into fstrings
brianbok Feb 14, 2019
5151835
Merge branch 'master' into fstrings
brianbok Feb 22, 2019
0e428fe
implement abstract node
brianbok Feb 25, 2019
065e503
Use stringreader instead of memorybuffer
brianbok Feb 26, 2019
1179f9a
Case pattern matching
brianbok Feb 26, 2019
1275f9d
Merge branch 'master' into fstrings
brianbok Feb 26, 2019
18bbd15
Many many tests
brianbok Feb 27, 2019
57f280c
Fixed and refactoring
brianbok Feb 27, 2019
ca0bc4f
Parsing of concat fstrings
brianbok Feb 28, 2019
5a91dd3
Refactoring
brianbok Feb 28, 2019
0defaaf
Adding parsing of conversion and format parameters
brianbok Feb 28, 2019
0d69dbf
Refactoring parsing
brianbok Feb 28, 2019
1130c65
Error handling
brianbok Feb 28, 2019
6ed2e8b
Nit
brianbok Feb 28, 2019
4890ad7
Newline nit
brianbok Feb 28, 2019
8966638
Concat of strings refactor
brianbok Mar 1, 2019
a2acf59
PR comments fixes
brianbok Mar 1, 2019
b8ccf25
Error handling
brianbok Mar 2, 2019
d9f2005
Test more fstring errors
brianbok Mar 2, 2019
ccf8b18
Parsing logic
brianbok Mar 4, 2019
281ca6b
WIP
brianbok Mar 5, 2019
1d93edb
Covering more error cases
brianbok Mar 6, 2019
7b5e38b
Rename fstring expression and create new tokenkind
brianbok Mar 6, 2019
15ddd86
Expressions public fields changed
brianbok Mar 6, 2019
585ed6b
Using node type instead of expression
brianbok Mar 6, 2019
071d3a8
order of fields in parseroptions
brianbok Mar 6, 2019
b69f1a7
Use of resources
brianbok Mar 7, 2019
b385231
Changes to parsing process
brianbok Mar 8, 2019
d9815e0
Parser tests
brianbok Mar 8, 2019
8568d4f
Async walk
brianbok Mar 8, 2019
f02f56a
Using more resource strings
brianbok Mar 8, 2019
fc4a0d2
Fstring errors
brianbok Mar 8, 2019
bf1df62
Merge branch 'master' into fstrings
brianbok Mar 8, 2019
dc65c5d
Parsing errors
brianbok Mar 8, 2019
de24848
Tests and handling backslash
brianbok Mar 8, 2019
f7aa549
Error handling
brianbok Mar 8, 2019
ecd9425
Assert
brianbok Mar 8, 2019
8d38473
Options to parser
brianbok Mar 8, 2019
8bd1c0b
Resources strings
brianbok Mar 8, 2019
38a09eb
Nit
brianbok Mar 8, 2019
0cc53f9
Errors adding with correct line number
brianbok Mar 8, 2019
62421ae
reusing method in error sink
brianbok Mar 8, 2019
03c5acb
Merge branch 'master' into fstrings
brianbok Mar 8, 2019
fd19b7e
fixes
brianbok Mar 11, 2019
282448b
Merge branch 'master' into fstrings
brianbok Mar 11, 2019
6d4ca5a
Modeling format specifier
brianbok Mar 12, 2019
5ddbaa5
format specifier node
brianbok Mar 12, 2019
90bcb72
Change fstring constructor, use readonly list
brianbok Mar 12, 2019
de6ed7e
Array instead of list
brianbok Mar 12, 2019
63c3df1
Tokenizer changes
brianbok Mar 13, 2019
bb94a3a
Expression location
brianbok Mar 13, 2019
3217512
Hovering test
brianbok Mar 13, 2019
4b9f738
Empty lambda issue
brianbok Mar 13, 2019
8ba4bbc
More tests
brianbok Mar 13, 2019
f5d6f3f
Lambda parenthesis warning
brianbok Mar 13, 2019
0551bdb
Merge branch 'lambda-parser' into fstrings
brianbok Mar 13, 2019
f31b80a
Error message fix
brianbok Mar 13, 2019
108b296
Exact location and better error message
brianbok Mar 13, 2019
e250039
Merge branch 'lambda-parser' into fstrings
brianbok Mar 13, 2019
96421ec
Lambda parenthesis now an error
brianbok Mar 13, 2019
afda431
Added test for lambda without parenthesis
brianbok Mar 13, 2019
fe3c968
Format specifier with '!' inside
brianbok Mar 13, 2019
e5233ea
Newline inside expression
brianbok Mar 13, 2019
bb04929
ParserOptions simplification
brianbok Mar 13, 2019
46d14a2
Unicode escaping tests
brianbok Mar 13, 2019
8aac770
Another test case
brianbok Mar 13, 2019
35bd88b
Test file was wrong
brianbok Mar 14, 2019
9a72fc2
Using 'NoParameters
brianbok Mar 14, 2019
e2dfe49
Merge branch 'lambda-parser' into fstrings
brianbok Mar 14, 2019
e57c39a
Tokenizer changes
brianbok Mar 13, 2019
a069a9b
Tokenizer test
brianbok Mar 14, 2019
01d9e8e
Implicitly typed array
brianbok Mar 14, 2019
d69af28
More implicitly typed arrays
brianbok Mar 14, 2019
1df9f37
Merge branch 'lambda-parser' into fstrings
brianbok Mar 14, 2019
78f4f61
More detail in test
brianbok Mar 14, 2019
558238f
Sinplifying changes
brianbok Mar 14, 2019
8082730
Tests
brianbok Mar 14, 2019
859eb79
Sinplifying changes
brianbok Mar 14, 2019
74a3e46
Merge branch 'master' into tokenizer-indexes
brianbok Mar 14, 2019
f20c6c1
Errors with initial location
brianbok Mar 14, 2019
01252c3
Merge branch 'master' into fstrings
brianbok Mar 14, 2019
b4f600d
Merge branch 'tokenizer-indexes' into fstrings
brianbok Mar 14, 2019
9a41ba7
Hovering over incomplete fstrings
brianbok Mar 14, 2019
1caece9
Test for invalid conversion character
brianbok Mar 14, 2019
4f310ce
Adding types for fstrings
brianbok Mar 14, 2019
81cbd6f
Diff looks much smaller now, using datarows
brianbok Mar 15, 2019
e404e00
Avoiding copy pasting parseErrors method
brianbok Mar 15, 2019
83b28ec
Nit
brianbok Mar 15, 2019
1ab74f9
Nit tokenizer tests
brianbok Mar 15, 2019
9f40f62
Merge branch 'tokenizer-indexes' into fstrings
brianbok Mar 15, 2019
0a5edf8
FormattedValue is now subtype of node
brianbok Mar 15, 2019
e37d932
Indexes in errors corrected
brianbok Mar 15, 2019
671e8aa
Better error message
brianbok Mar 15, 2019
a1e9cf7
Merge branch 'tokenizer-indexes' into fstrings
brianbok Mar 15, 2019
18ffd16
Added test case
brianbok Mar 15, 2019
fd8b11c
Better error handling`
brianbok Mar 15, 2019
6bf65de
Offset instead of displacement
brianbok Mar 15, 2019
6730729
Merge branch 'tokenizer-indexes' into fstrings
brianbok Mar 15, 2019
ac12f28
Parsing string logic looks more like original
brianbok Mar 15, 2019
bc779b8
More error tests and handling
brianbok Mar 15, 2019
07f4277
Changing to internal for hiding parser and builder
brianbok Mar 15, 2019
9c1f8d4
Order fields
brianbok Mar 15, 2019
29930f0
Renaming method
brianbok Mar 15, 2019
5fb3efb
adding comment
brianbok Mar 15, 2019
35d3389
Merge branch 'master' into fstrings
brianbok Mar 15, 2019
7904599
deleted newline
brianbok Mar 15, 2019
05ab9e9
Add lambda test case
brianbok Mar 15, 2019
1ec5877
Missing newline
brianbok Mar 15, 2019
9e93e75
Deleting builder: it was overcomplicated
brianbok Mar 15, 2019
626d011
Adding tests: no completion inside fstrings
brianbok Mar 15, 2019
fca929a
Tests: No completion inside fstrings
brianbok Mar 15, 2019
2740e17
No hovering over fstrings (constant part)
brianbok Mar 15, 2019
8f86053
Deleting manual index calculating for setting loc
brianbok Mar 18, 2019
9d88c95
Better handling of whitespace
brianbok Mar 18, 2019
8062800
Switch instead of dictionary
brianbok Mar 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ public IMember GetValueFromExpression(Expression expr, LookupOptions options) {
case LambdaExpression lambda:
m = GetValueFromLambda(lambda);
break;
case FString fString:
m = GetValueFromFString(fString);
break;
case FormatSpecifier formatSpecifier:
m = GetValueFromFormatSpecifier(formatSpecifier);
break;
default:
m = GetValueFromBinaryOp(expr) ?? GetConstantFromLiteral(expr, options);
break;
Expand All @@ -154,6 +160,14 @@ public IMember GetValueFromExpression(Expression expr, LookupOptions options) {
return m;
}

private IMember GetValueFromFormatSpecifier(FormatSpecifier formatSpecifier) {
return new PythonFString(formatSpecifier.Unparsed, Interpreter, GetLoc(formatSpecifier));
}

private IMember GetValueFromFString(FString fString) {
return new PythonFString(fString.Unparsed, Interpreter, GetLoc(fString));
}

private IMember GetValueFromName(NameExpression expr, LookupOptions options) {
if (expr == null || string.IsNullOrEmpty(expr.Name)) {
return null;
Expand Down
18 changes: 18 additions & 0 deletions src/Analysis/Ast/Impl/Values/PythonString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing;

Expand All @@ -32,4 +33,21 @@ internal sealed class PythonUnicodeString : PythonConstant {
public PythonUnicodeString(string s, IPythonInterpreter interpreter, LocationInfo location = null)
: base(s, interpreter.GetBuiltinType(interpreter.GetUnicodeTypeId()), location) { }
}

internal sealed class PythonFString : PythonInstance, IEquatable<PythonFString> {
public readonly string UnparsedFString;

public PythonFString(string unparsedFString, IPythonInterpreter interpreter, LocationInfo location = null)
: base(interpreter.GetBuiltinType(interpreter.GetUnicodeTypeId()), location) {
UnparsedFString = unparsedFString;
}


public bool Equals(PythonFString other) {
if (!base.Equals(other)) {
return false;
}
return UnparsedFString?.Equals(other?.UnparsedFString) == true;
}
}
}
9 changes: 9 additions & 0 deletions src/Analysis/Ast/Test/TypingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ from typing import AnyStr
.And.HaveVariable("y").OfType("AnyStr");
}

[TestMethod, Priority(0)]
public async Task FStringIsStringType() {
const string code = @"
x = f'{1}'
";
var analysis = await GetAnalysisAsync(code);
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Str);
}

[TestMethod, Priority(0)]
public async Task OptionalNone() {
const string code = @"
Expand Down
3 changes: 2 additions & 1 deletion src/LanguageServer/Impl/Sources/HoverSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public Hover GetHover(IDocumentAnalysis analysis, SourceLocation location) {
ExpressionLocator.FindExpression(analysis.Ast, location,
FindExpressionOptions.Hover, out var node, out var statement, out var scope);

if (node is ConstantExpression || !(node is Expression expr)) {
if (node is ConstantExpression || node is FString || !(node is Expression expr)) {
// node is FString only if it didn't save an f-string subexpression
return null; // No hover for literals.
}

Expand Down
11 changes: 11 additions & 0 deletions src/LanguageServer/Test/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,17 @@ public async Task NoCompletionInOpenString() {
result.Should().HaveNoCompletion();
}

[DataRow("f'.")]
[DataRow("f'a.")]
[DataRow("f'a.'")]
[DataTestMethod, Priority(0)]
public async Task NoCompletionInFStringConstant(string openFString) {
var analysis = await GetAnalysisAsync(openFString);
var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion);
var result = cs.GetCompletions(analysis, new SourceLocation(1, 5));
result.Should().HaveNoCompletion();
}

[TestMethod, Priority(0)]
public async Task NoCompletionBadImportExpression() {
var analysis = await GetAnalysisAsync("import os,.");
Expand Down
18 changes: 18 additions & 0 deletions src/LanguageServer/Test/HoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,24 @@ import os.path as PATH
AssertHover(hs, analysis, new SourceLocation(2, 20), $"module {name}*", new SourceSpan(2, 19, 2, 23));
}

[TestMethod, Priority(0)]
public async Task FStringExpressions() {
const string code = @"
some = ''
f'{some'
Fr'{some'
f'{some}'
f'hey {some}'
";
var analysis = await GetAnalysisAsync(code);
var hs = new HoverSource(new PlainTextDocumentationSource());
AssertHover(hs, analysis, new SourceLocation(3, 4), @"some: str", new SourceSpan(3, 4, 3, 8));
AssertHover(hs, analysis, new SourceLocation(4, 5), @"some: str", new SourceSpan(4, 5, 4, 9));
AssertHover(hs, analysis, new SourceLocation(5, 4), @"some: str", new SourceSpan(5, 4, 5, 8));
hs.GetHover(analysis, new SourceLocation(6, 3)).Should().BeNull();
AssertHover(hs, analysis, new SourceLocation(6, 8), @"some: str", new SourceSpan(6, 8, 6, 12));
}

private static void AssertHover(HoverSource hs, IDocumentAnalysis analysis, SourceLocation position, string hoverText, SourceSpan? span = null) {
var hover = hs.GetHover(analysis, position);

Expand Down
84 changes: 84 additions & 0 deletions src/Parsing/Impl/Ast/FString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Python.Parsing.Ast {
public class FString : Expression {
private readonly Node[] _children;
private readonly string _openQuotes;

public FString(Node[] children, string openQuotes, string unparsed) {
_children = children;
_openQuotes = openQuotes;
Unparsed = unparsed;
}

public override IEnumerable<Node> GetChildNodes() {
return _children;
}

public readonly string Unparsed;

public override void Walk(PythonWalker walker) {
if (walker.Walk(this)) {
foreach (var child in _children) {
child.Walk(walker);
}
}
walker.PostWalk(this);
}

public async override Task WalkAsync(PythonWalkerAsync walker, CancellationToken cancellationToken = default) {
if (await walker.WalkAsync(this, cancellationToken)) {
foreach (var child in _children) {
await child.WalkAsync(walker, cancellationToken);
}
}
await walker.PostWalkAsync(this, cancellationToken);
}

internal override void AppendCodeString(StringBuilder res, PythonAst ast, CodeFormattingOptions format) {
var verbatimPieces = this.GetVerbatimNames(ast);
var verbatimComments = this.GetListWhiteSpace(ast);
if (verbatimPieces != null) {
// string+ / bytes+, such as "abc" "abc", which can spawn multiple lines, and
// have comments in between the peices.
for (var i = 0; i < verbatimPieces.Length; i++) {
if (verbatimComments != null && i < verbatimComments.Length) {
format.ReflowComment(res, verbatimComments[i]);
}
res.Append(verbatimPieces[i]);
}
} else {
format.ReflowComment(res, this.GetPreceedingWhiteSpaceDefaultNull(ast));
if (this.GetExtraVerbatimText(ast) != null) {
res.Append(this.GetExtraVerbatimText(ast));
} else {
RecursiveAppendRepr(res, ast, format);
}
}
}

private void RecursiveAppendRepr(StringBuilder res, PythonAst ast, CodeFormattingOptions format) {
res.Append('f');
res.Append(_openQuotes);
foreach (var child in _children) {
AppendChild(res, ast, format, child);
}
res.Append(_openQuotes);
}

private static void AppendChild(StringBuilder res, PythonAst ast, CodeFormattingOptions format, Node child) {
if (child is ConstantExpression expr) {
// Non-Verbatim AppendCodeString for ConstantExpression adds quotes around string
// Remove those quotes
var childStrBuilder = new StringBuilder();
child.AppendCodeString(childStrBuilder, ast, format);
res.Append(childStrBuilder.ToString().Trim('\''));
} else {
child.AppendCodeString(res, ast, format);
}
}
}
}
58 changes: 58 additions & 0 deletions src/Parsing/Impl/Ast/FormatSpecifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Python.Parsing.Ast {
public class FormatSpecifier : Expression {
private readonly Node[] _children;

public FormatSpecifier(Node[] children, string unparsed) {
_children = children;
Unparsed = unparsed;
}

public override IEnumerable<Node> GetChildNodes() {
return _children;
}

public readonly string Unparsed;

public override void Walk(PythonWalker walker) {
if (walker.Walk(this)) {
foreach (var child in _children) {
child.Walk(walker);
}
}
walker.PostWalk(this);
}

public async override Task WalkAsync(PythonWalkerAsync walker, CancellationToken cancellationToken = default) {
if (await walker.WalkAsync(this, cancellationToken)) {
foreach (var child in _children) {
await child.WalkAsync(walker, cancellationToken);
}
}
await walker.PostWalkAsync(this, cancellationToken);
}

internal override void AppendCodeString(StringBuilder res, PythonAst ast, CodeFormattingOptions format) {
// There is no leading f
foreach (var child in _children) {
AppendChild(res, ast, format, child);
}
}

private static void AppendChild(StringBuilder res, PythonAst ast, CodeFormattingOptions format, Node child) {
if (child is ConstantExpression expr) {
// Non-Verbatim AppendCodeString for ConstantExpression adds quotes around string
// Remove those quotes
var childStrBuilder = new StringBuilder();
child.AppendCodeString(childStrBuilder, ast, format);
res.Append(childStrBuilder.ToString().Trim('\''));
} else {
child.AppendCodeString(res, ast, format);
}
}
}
}
56 changes: 56 additions & 0 deletions src/Parsing/Impl/Ast/FormattedValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Python.Parsing.Ast {
public class FormattedValue : Node {
public FormattedValue(Expression value, char? conversion, Expression formatSpecifier) {
Value = value;
FormatSpecifier = formatSpecifier;
Conversion = conversion;
}

public Expression Value { get; }
public Expression FormatSpecifier { get; }
public char? Conversion { get; }

public override IEnumerable<Node> GetChildNodes() {
yield return Value;
if (FormatSpecifier != null) {
yield return FormatSpecifier;
}
}

public override void Walk(PythonWalker walker) {
if (walker.Walk(this)) {
Value.Walk(walker);
FormatSpecifier?.Walk(walker);
}
walker.PostWalk(this);
}

public async override Task WalkAsync(PythonWalkerAsync walker, CancellationToken cancellationToken = default) {
if (await walker.WalkAsync(this, cancellationToken)) {
await Value.WalkAsync(walker, cancellationToken);
await FormatSpecifier?.WalkAsync(walker, cancellationToken);
}
await walker.PostWalkAsync(this, cancellationToken);
}

internal override void AppendCodeString(StringBuilder res, PythonAst ast, CodeFormattingOptions format) {
res.Append('{');
Value.AppendCodeString(res, ast, format);
if (Conversion.HasValue) {
res.Append('!');
res.Append(Conversion.Value);
}
if (FormatSpecifier != null) {
res.Append(':');
FormatSpecifier.AppendCodeString(res, ast, format);
}
res.Append('}');
}
}
}
21 changes: 21 additions & 0 deletions src/Parsing/Impl/Ast/PythonWalker.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ public virtual void PostWalk(ErrorStatement node) { }
// DecoratorStatement
public virtual bool Walk(DecoratorStatement node) { return true; }
public virtual void PostWalk(DecoratorStatement node) { }

// FString
public virtual bool Walk(FString node) { return true; }
public virtual void PostWalk(FString node) { }

// FormatSpecifier
public virtual bool Walk(FormatSpecifier node) { return true; }
public virtual void PostWalk(FormatSpecifier node) { }

// FormattedValue
public virtual bool Walk(FormattedValue node) { return true; }
public virtual void PostWalk(FormattedValue node) { }
}


Expand Down Expand Up @@ -794,6 +806,15 @@ private bool Contains(Statement stmt) {

// DecoratorStatement
public override bool Walk(DecoratorStatement node) { return Contains(node); }

// FString
public override bool Walk(FString node) { return Location >= node.StartIndex && Location <= node.EndIndex; }

// FormatSpecifier
public override bool Walk(FormatSpecifier node) { return Location >= node.StartIndex && Location <= node.EndIndex; }

// FormattedValue
public override bool Walk(FormattedValue node) { return Location >= node.StartIndex && Location <= node.EndIndex; }
}

}
24 changes: 24 additions & 0 deletions src/Parsing/Impl/Ast/PythonWalkerAsync.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ public class PythonWalkerAsync {
// DecoratorStatement
public virtual Task<bool> WalkAsync(DecoratorStatement node, CancellationToken cancellationToken = default) => Task.FromResult(true);
public virtual Task PostWalkAsync(DecoratorStatement node, CancellationToken cancellationToken = default) => Task.CompletedTask;

// FString
public virtual Task<bool> WalkAsync(FString node, CancellationToken cancellationToken = default) => Task.FromResult(true);
public virtual Task PostWalkAsync(FString node, CancellationToken cancellationToken = default) => Task.CompletedTask;

// FormatSpecifier
public virtual Task<bool> WalkAsync(FormatSpecifier node, CancellationToken cancellationToken = default) => Task.FromResult(true);
public virtual Task PostWalkAsync(FormatSpecifier node, CancellationToken cancellationToken = default) => Task.CompletedTask;

// FormattedValue
public virtual Task<bool> WalkAsync(FormattedValue node, CancellationToken cancellationToken = default) => Task.FromResult(true);
public virtual Task PostWalkAsync(FormattedValue node, CancellationToken cancellationToken = default) => Task.CompletedTask;
}


Expand Down Expand Up @@ -862,5 +874,17 @@ public override Task<bool> WalkAsync(ErrorStatement node, CancellationToken canc
// DecoratorStatement
public override Task<bool> WalkAsync(DecoratorStatement node, CancellationToken cancellationToken = default)
=> Task.FromResult(Contains(node));

// FString
public override Task<bool> WalkAsync(FString node, CancellationToken cancellationToken = default)
=> Task.FromResult(Location >= node.StartIndex && Location <= node.EndIndex);

// FormatSpecifier
public override Task<bool> WalkAsync(FormatSpecifier node, CancellationToken cancellationToken = default)
=> Task.FromResult(Location >= node.StartIndex && Location <= node.EndIndex);

// FormattedValue
public override Task<bool> WalkAsync(FormattedValue node, CancellationToken cancellationToken = default)
=> Task.FromResult(Location >= node.StartIndex && Location <= node.EndIndex);
}
}
Loading