Skip to content

Commit 3c96d0e

Browse files
committed
C#: Extract alignment and format clauses of string interpolation expressions.
1 parent e6c3be9 commit 3c96d0e

File tree

5 files changed

+86
-16
lines changed

5 files changed

+86
-16
lines changed

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/InterpolatedString.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.IO;
2-
using Microsoft.CodeAnalysis;
32
using Microsoft.CodeAnalysis.CSharp;
43
using Microsoft.CodeAnalysis.CSharp.Syntax;
54
using Semmle.Extraction.Kinds;
@@ -21,15 +20,7 @@ protected override void PopulateExpression(TextWriter trapFile)
2120
{
2221
case SyntaxKind.Interpolation:
2322
var interpolation = (InterpolationSyntax)c;
24-
var exp = interpolation.Expression;
25-
if (Context.GetTypeInfo(exp).Type is ITypeSymbol type && !type.ImplementsIFormattable())
26-
{
27-
ImplicitToString.Create(Context, exp, this, child++);
28-
}
29-
else
30-
{
31-
Create(Context, exp, this, child++);
32-
}
23+
new InterpolatedStringInsert(Context, interpolation, this, child++);
3324
break;
3425
case SyntaxKind.InterpolatedStringText:
3526
// Create a string literal
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using Semmle.Extraction.Kinds;
4+
5+
namespace Semmle.Extraction.CSharp.Entities.Expressions
6+
{
7+
internal class InterpolatedStringInsert : Expression
8+
{
9+
public InterpolatedStringInsert(Context cx, InterpolationSyntax syntax, IExpressionParentEntity parent, int child) :
10+
base(new ExpressionInfo(cx, null, cx.CreateLocation(syntax.GetLocation()), ExprKind.INTERPOLATED_STRING_INSERT, parent, child, isCompilerGenerated: false, null))
11+
{
12+
var exp = syntax.Expression;
13+
if (Context.GetTypeInfo(exp).Type is ITypeSymbol type && !type.ImplementsIFormattable())
14+
{
15+
ImplicitToString.Create(Context, exp, this, 0);
16+
}
17+
{
18+
Create(Context, exp, this, 0);
19+
}
20+
21+
// Hardcode the child number of the optional alignment clause to 1 and format clause to 2.
22+
// This simplifies the logic in QL.
23+
if (syntax.AlignmentClause?.Value is ExpressionSyntax alignment)
24+
{
25+
Create(Context, alignment, this, 1);
26+
}
27+
28+
if (syntax.FormatClause is InterpolationFormatClauseSyntax format)
29+
{
30+
var f = format.FormatStringToken.ValueText;
31+
var t = AnnotatedTypeSymbol.CreateNotAnnotated(Context.Compilation.GetSpecialType(SpecialType.System_String));
32+
new Expression(new ExpressionInfo(Context, t, Context.CreateLocation(format.GetLocation()), ExprKind.UTF16_STRING_LITERAL, this, 2, isCompilerGenerated: false, f));
33+
}
34+
35+
}
36+
}
37+
38+
}

csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public enum ExprKind
132132
UTF8_STRING_LITERAL = 135,
133133
COLLECTION = 136,
134134
SPREAD_ELEMENT = 137,
135+
INTERPOLATED_STRING_INSERT = 138,
135136
DEFINE_SYMBOL = 999,
136137
}
137138
}

csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ private import semmle.code.csharp.TypeRef
3434
* `as` expression (`AsExpr`), a cast (`CastExpr`), a `typeof` expression
3535
* (`TypeofExpr`), a `default` expression (`DefaultValueExpr`), an `await`
3636
* expression (`AwaitExpr`), a `nameof` expression (`NameOfExpr`), an
37-
* interpolated string (`InterpolatedStringExpr`), a qualifiable expression
38-
* (`QualifiableExpr`), or a literal (`Literal`).
37+
* interpolated string (`InterpolatedStringExpr`), an interpolated string
38+
* insert (`InterpolatedStringInsertExpr`), a qualifiable expression (`QualifiableExpr`),
39+
* or a literal (`Literal`).
3940
*/
4041
class Expr extends ControlFlowElement, @expr {
4142
override Location getALocation() { expr_location(this, result) }
@@ -917,6 +918,40 @@ class NameOfExpr extends Expr, @nameof_expr {
917918
override string getAPrimaryQlClass() { result = "NameOfExpr" }
918919
}
919920

921+
/**
922+
* An interpolated string insert, for example `{pi, align:F3}` on line 3 in
923+
*
924+
* ```csharp
925+
* void Pi() {
926+
* float pi = 3.14159f;
927+
* Console.WriteLine($"Hello Pi, {pi,6:F3}");
928+
* }
929+
* ```
930+
*/
931+
class InterpolatedStringInsertExpr extends Expr, @interpolated_string_insert_expr {
932+
override string toString() { result = "{...}" }
933+
934+
override string getAPrimaryQlClass() { result = "InterpolatedStringInsertExpr" }
935+
936+
/**
937+
* Gets the insert expression in this interpolated string insert. For
938+
* example, the insert expression in `{pi, align:F3}` is `pi`.
939+
*/
940+
Expr getInsert() { result = this.getChild(0) }
941+
942+
/**
943+
* Gets the alignment expression in this interpolated string insert, if any.
944+
* For example, the alignment expression in `{pi, align:F3}` is `align`.
945+
*/
946+
Expr getAlignment() { result = this.getChild(1) }
947+
948+
/**
949+
* Gets the format expression in this interpolated string insert, if any.
950+
* For example, the format expression in `{pi, align:F3}` is `F3`.
951+
*/
952+
StringLiteral getFormat() { result = this.getChild(2) }
953+
}
954+
920955
/**
921956
* An interpolated string, for example `$"Hello, {name}!"` on line 2 in
922957
*
@@ -931,16 +966,20 @@ class InterpolatedStringExpr extends Expr, @interpolated_string_expr {
931966

932967
override string getAPrimaryQlClass() { result = "InterpolatedStringExpr" }
933968

969+
/**
970+
* Gets the interpolated string insert at index `i` in this interpolated string, if any.
971+
* For example, the interpolated string insert at index `i = 1` is `{pi, align:F3}`
972+
* in `$"Hello, {pi, align:F3}!"`.
973+
*/
974+
InterpolatedStringInsertExpr getInterpolatedInsert(int i) { result = this.getChild(i) }
975+
934976
/**
935977
* Gets the insert at index `i` in this interpolated string, if any. For
936978
* example, the insert at index `i = 1` is `name` in `$"Hello, {name}!"`.
937979
* Note that there is no insert at index `i = 0`, but instead a text
938980
* element (`getText(0)` gets the text).
939981
*/
940-
Expr getInsert(int i) {
941-
result = this.getChild(i) and
942-
not result instanceof StringLiteral
943-
}
982+
Expr getInsert(int i) { result = this.getInterpolatedInsert(i).getInsert() }
944983

945984
/**
946985
* Gets the text element at index `i` in this interpolated string, if any.

csharp/ql/lib/semmlecode.csharp.dbscheme

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ case @expr.kind of
11681168
/* C# 12.0 */
11691169
| 136 = @collection_expr
11701170
| 137 = @spread_element_expr
1171+
| 138 = @interpolated_string_insert_expr
11711172
/* Preprocessor */
11721173
| 999 = @define_symbol_expr
11731174
;

0 commit comments

Comments
 (0)