Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions python/extractor/semmle/python/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ def __init__(self, prefix, text, s):
self.text = text
self.s = s

class TemplateStringPart(AstBase):
'''A string constituent of a template string literal'''

__slots__ = "text", "s",

def __init__(self, text, s):
self.text = text
self.s = s

class alias(AstBase):
__slots__ = "value", "asname",

Expand Down Expand Up @@ -356,6 +365,19 @@ class JoinedStr(expr):
def __init__(self, values):
self.values = values

class TemplateString(expr):
__slots__ = "prefix", "values",

def __init__(self, prefix, values):
self.prefix = prefix
self.values = values

class JoinedTemplateString(expr):
__slots__ = "strings",

def __init__(self, strings):
self.strings = strings


class Lambda(expr):
__slots__ = "args", "inner_scope",
Expand Down
17 changes: 17 additions & 0 deletions python/extractor/semmle/python/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,20 @@

FormattedValue = ClassNode("FormattedValue", expr, descriptive_name='formatted value')


AnnAssign = ClassNode("AnnAssign", stmt, descriptive_name='annotated assignment')

AssignExpr = ClassNode('AssignExpr', expr, "assignment expression")

SpecialOperation = ClassNode('SpecialOperation', expr, "special operation")

TemplateString = ClassNode('TemplateString', expr, 'template string literal')

template_string_list = ListNode(TemplateString)

JoinedTemplateString = ClassNode("JoinedTemplateString", expr, descriptive_name='joined template string')
TemplateStringPart = ClassNode('TemplateStringPart', None, "string part of a template string")

type_parameter = ClassNode('type_parameter', descriptive_name='type parameter')
type_parameter.field('location', location)
type_parameter_list = ListNode(type_parameter)
Expand Down Expand Up @@ -435,6 +443,9 @@
Subscript.field('index', expr)
Subscript.field('ctx', expr_context, 'context')

TemplateString.field('prefix', string, 'prefix')
TemplateString.field('values', expr_list, 'values')

Try.field('body', stmt_list)
Try.field('orelse', stmt_list, 'else block')
Try.field('handlers', stmt_list, 'exception handlers')
Expand Down Expand Up @@ -484,10 +495,16 @@
StringPart.field('text', string)
StringPart.field('location', location)

TemplateStringPart.field('text', string)
TemplateStringPart.field('location', location)


Await.field('value', expr, 'expression waited upon')

FormattedStringLiteral.field('values', expr_list)

JoinedTemplateString.field('strings', template_string_list)

FormattedValue.field('value', expr, "expression to be formatted")
FormattedValue.field('conversion', string, 'type conversion')
FormattedValue.field('format_spec', FormattedStringLiteral, 'format specifier')
Expand Down
2 changes: 2 additions & 0 deletions python/extractor/semmle/python/parser/tsg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ def get_location_info(attrs):
ast.Print: ("values",),
ast.Set: ("elts",),
ast.Str: ("implicitly_concatenated_parts",),
ast.TemplateString: ("values",),
ast.JoinedTemplateString: ("strings",),
ast.TypeAlias: ("type_parameters",),
ast.Try: ("body", "handlers", "orelse", "finalbody"),
ast.Tuple: ("elts",),
Expand Down
194 changes: 194 additions & 0 deletions python/extractor/tests/parser/template_strings_new.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
Module: [1, 0] - [18, 0]
body: [
Assign: [1, 0] - [1, 14]
targets: [
Name: [1, 0] - [1, 4]
variable: Variable('name', None)
ctx: Store
]
value:
Str: [1, 7] - [1, 14]
s: 'World'
prefix: '"'
implicitly_concatenated_parts: None
Assign: [2, 0] - [2, 15]
targets: [
Name: [2, 0] - [2, 5]
variable: Variable('value', None)
ctx: Store
]
value:
Num: [2, 8] - [2, 15]
n: 42.5678
text: '42.5678'
Assign: [3, 0] - [3, 15]
targets: [
Name: [3, 0] - [3, 5]
variable: Variable('first', None)
ctx: Store
]
value:
Str: [3, 8] - [3, 15]
s: 'first'
prefix: '"'
implicitly_concatenated_parts: None
Assign: [4, 0] - [4, 17]
targets: [
Name: [4, 0] - [4, 6]
variable: Variable('second', None)
ctx: Store
]
value:
Str: [4, 9] - [4, 17]
s: 'second'
prefix: '"'
implicitly_concatenated_parts: None
If: [6, 0] - [6, 5]
test:
Num: [6, 3] - [6, 4]
n: 1
text: '1'
body: [
Expr: [7, 4] - [7, 7]
value:
TemplateString: [7, 4] - [7, 7]
prefix: 't"'
values: []
]
orelse: None
If: [8, 0] - [8, 5]
test:
Num: [8, 3] - [8, 4]
n: 2
text: '2'
body: [
Expr: [9, 4] - [9, 21]
value:
TemplateString: [9, 4] - [9, 21]
prefix: 't"'
values: [
TemplateStringPart: [9, 6] - [9, 13]
text: '"Hello, "'
s: 'Hello, '
Name: [9, 14] - [9, 18]
variable: Variable('name', None)
ctx: Load
TemplateStringPart: [9, 19] - [9, 20]
text: '"!"'
s: '!'
]
]
orelse: None
If: [10, 0] - [10, 5]
test:
Num: [10, 3] - [10, 4]
n: 3
text: '3'
body: [
Expr: [11, 4] - [11, 42]
value:
TemplateString: [11, 4] - [11, 42]
prefix: 't"'
values: [
TemplateStringPart: [11, 6] - [11, 13]
text: '"Value: "'
s: 'Value: '
Name: [11, 14] - [11, 19]
variable: Variable('value', None)
ctx: Load
TemplateStringPart: [11, 24] - [11, 31]
text: '", Hex: "'
s: ', Hex: '
Name: [11, 32] - [11, 37]
variable: Variable('value', None)
ctx: Load
]
]
orelse: None
If: [12, 0] - [12, 5]
test:
Num: [12, 3] - [12, 4]
n: 4
text: '4'
body: [
Expr: [13, 4] - [13, 29]
value:
TemplateString: [13, 4] - [13, 29]
prefix: 't"'
values: [
TemplateStringPart: [13, 6] - [13, 28]
text: '"Just a regular string."'
s: 'Just a regular string.'
]
]
orelse: None
If: [14, 0] - [14, 5]
test:
Num: [14, 3] - [14, 4]
n: 5
text: '5'
body: [
Expr: [15, 4] - [15, 50]
value:
TemplateString: [15, 4] - [15, 50]
prefix: 't"'
values: [
TemplateStringPart: [15, 6] - [15, 15]
text: '"Multiple "'
s: 'Multiple '
Name: [15, 16] - [15, 21]
variable: Variable('first', None)
ctx: Load
TemplateStringPart: [15, 22] - [15, 27]
text: '" and "'
s: ' and '
Name: [15, 28] - [15, 34]
variable: Variable('second', None)
ctx: Load
TemplateStringPart: [15, 35] - [15, 49]
text: '" placeholders."'
s: ' placeholders.'
]
]
orelse: None
If: [16, 0] - [16, 5]
test:
Num: [16, 3] - [16, 4]
n: 6
text: '6'
body: [
Expr: [17, 4] - [17, 66]
value:
JoinedTemplateString: [17, 4] - [17, 66]
strings: [
TemplateString: [17, 4] - [17, 31]
prefix: 't"'
values: [
TemplateStringPart: [17, 6] - [17, 30]
text: '"Implicit concatenation: "'
s: 'Implicit concatenation: '
]
TemplateString: [17, 32] - [17, 49]
prefix: 't"'
values: [
TemplateStringPart: [17, 34] - [17, 41]
text: '"Hello, "'
s: 'Hello, '
Name: [17, 42] - [17, 46]
variable: Variable('name', None)
ctx: Load
TemplateStringPart: [17, 47] - [17, 48]
text: '"!"'
s: '!'
]
TemplateString: [17, 50] - [17, 66]
prefix: 't"'
values: [
TemplateStringPart: [17, 52] - [17, 65]
text: '" How are you?"'
s: ' How are you?'
]
]
]
orelse: None
]
17 changes: 17 additions & 0 deletions python/extractor/tests/parser/template_strings_new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name = "World"
value = 42.5678
first = "first"
second = "second"

if 1:
t""
if 2:
t"Hello, {name}!"
if 3:
t"Value: {value:.2f}, Hex: {value:#x}"
if 4:
t"Just a regular string."
if 5:
t"Multiple {first} and {second} placeholders."
if 6:
t"Implicit concatenation: " t"Hello, {name}!" t" How are you?"
47 changes: 47 additions & 0 deletions python/extractor/tsg-python/python.tsg
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
(string string_content: (_) @part)
{ let @part.node = (ast-node @part "StringPart") }

(template_string string_content: (_) @part)
{ let @part.node = (ast-node @part "TemplateStringPart") }

; A string concatenation that contains no interpolated expressions is just a `Str` (and its children
; will be `StringPart`s). A string concatenation that contains interpolated expressions is a
; `JoinedStr`, however.
Expand All @@ -142,6 +145,12 @@
}
}

(template_string) @tstring
{ let @tstring.node = (ast-node @tstring "TemplateString") }

(concatenated_template_string) @tstrings
{ let @tstrings.node = (ast-node @tstrings "JoinedTemplateString") }

(pair) @kvpair
{ let @kvpair.node = (ast-node @kvpair "KeyValuePair") }

Expand Down Expand Up @@ -2052,6 +2061,44 @@

;;;;;; End of JoinedStr (`f"foo"`)

;;;;;; JoinedTemplateString / TemplateString (`t"foo"`)

; Record the prefix of the template string.
(template_string) @tstring
{
attr (@tstring.node) prefix = (string-prefix @tstring)
}

; Attach raw children (string parts and interpolations) to the template string node.
(template_string (string_content) @part) @tmpl_any
{
edge @tmpl_any.node -> @part.node
attr (@tmpl_any.node -> @part.node) values = (named-child-index @part)
attr (@part.node) ctx = "load"
let safe_string = (concatenate-strings (string-safe-prefix @tmpl_any) (source-text @part) (string-quotes @tmpl_any))
attr (@part.node) s = safe_string
attr (@part.node) text = safe_string
}

(template_string (interpolation expression: (_) @part) @interp) @tmpl_any
{
edge @tmpl_any.node -> @part.node
attr (@tmpl_any.node -> @part.node) values = (named-child-index @interp)
attr (@part.node) ctx = "load"
}


; Concatenated template strings simply have a list-like field containing the template strings that
; are concatenated together.
(concatenated_template_string (template_string) @tstring) @tmpl_concat
{
edge @tmpl_concat.node -> @tstring.node
attr (@tmpl_concat.node -> @tstring.node) strings = (named-child-index @tstring)
attr (@tstring.node) ctx = "load"
}

;;;;;; End of JoinedTemplateString / TemplateString (`t"foo"`)



;;;;;; List (`[...]`)
Expand Down
Loading
Loading