mirror of
https://github.com/github/codeql.git
synced 2026-02-12 05:01:06 +01:00
Merge pull request #20708 from github/tausbn/python-add-support-for-template-string-literals
Python: Add support for template string literals
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
// We must wrap the DB types, as these cannot appear in argument lists
|
||||
class Expr_ extends @py_expr {
|
||||
string toString() { result = "Expr" }
|
||||
}
|
||||
|
||||
class ExprParent_ extends @py_expr_parent {
|
||||
string toString() { result = "ExprList" }
|
||||
}
|
||||
|
||||
query predicate py_exprs_without_template_strings(Expr_ id, int kind, ExprParent_ parent, int idx) {
|
||||
py_exprs(id, kind, parent, idx) and
|
||||
// From the dbscheme:
|
||||
//
|
||||
// case @py_expr.kind of
|
||||
// ...
|
||||
// | 39 = @py_SpecialOperation
|
||||
// | 40 = @py_TemplateString
|
||||
// | 41 = @py_JoinedTemplateString
|
||||
// | 42 = @py_TemplateStringPart;
|
||||
not kind in [40, 41, 42]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
description: Remove support for template string literals
|
||||
compatibility: backwards
|
||||
py_TemplateString_lists.rel: delete
|
||||
py_exprs.rel: run py_exprs.qlo py_exprs_without_template_strings
|
||||
@@ -56,6 +56,15 @@ class StringPart(AstBase):
|
||||
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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -186,12 +186,20 @@ FormattedStringLiteral.set_name("Fstring")
|
||||
|
||||
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', expr, "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)
|
||||
@@ -435,6 +443,9 @@ Subscript.field('value', expr)
|
||||
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')
|
||||
@@ -484,10 +495,15 @@ PlaceHolder.field('ctx', expr_context, 'context')
|
||||
StringPart.field('text', string)
|
||||
StringPart.field('location', location)
|
||||
|
||||
TemplateStringPart.field('text', string)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
@@ -273,6 +273,8 @@ list_fields = {
|
||||
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",),
|
||||
|
||||
@@ -10,7 +10,7 @@ from io import BytesIO
|
||||
|
||||
#Semantic version of extractor.
|
||||
#Update this if any changes are made
|
||||
VERSION = "7.1.6"
|
||||
VERSION = "7.1.7"
|
||||
|
||||
PY_EXTENSIONS = ".py", ".pyw"
|
||||
|
||||
|
||||
194
python/extractor/tests/parser/template_strings_new.expected
Normal file
194
python/extractor/tests/parser/template_strings_new.expected
Normal file
@@ -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
python/extractor/tests/parser/template_strings_new.py
Normal file
17
python/extractor/tests/parser/template_strings_new.py
Normal file
@@ -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?"
|
||||
@@ -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.
|
||||
@@ -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") }
|
||||
|
||||
@@ -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 (`[...]`)
|
||||
|
||||
@@ -140,15 +140,22 @@ pub mod extra_functions {
|
||||
}
|
||||
|
||||
fn safe(&self) -> Prefix {
|
||||
// Remove format (f/F) and template (t/T) flags when generating a safe prefix.
|
||||
Prefix {
|
||||
flags: self.flags.clone().replace("f", "").replace("F", ""),
|
||||
flags: self
|
||||
.flags
|
||||
.clone()
|
||||
.replace("f", "")
|
||||
.replace("F", "")
|
||||
.replace("t", "")
|
||||
.replace("T", ""),
|
||||
quotes: self.quotes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prefix(s: &str) -> Prefix {
|
||||
let flags_matcher = regex::Regex::new("^[bfurBFUR]{0,2}").unwrap();
|
||||
let flags_matcher = regex::Regex::new("^[bfurtBFURT]{0,2}").unwrap();
|
||||
let mut end = 0;
|
||||
let flags = match flags_matcher.find(s) {
|
||||
Some(m) => {
|
||||
@@ -170,7 +177,7 @@ pub mod extra_functions {
|
||||
quotes = "}";
|
||||
}
|
||||
Prefix {
|
||||
flags: flags.to_lowercase().to_owned(),
|
||||
flags: flags.to_owned(),
|
||||
quotes: quotes.to_owned(),
|
||||
}
|
||||
}
|
||||
@@ -198,6 +205,12 @@ pub mod extra_functions {
|
||||
let p = get_prefix("\"\"\"\"\"\"");
|
||||
assert_eq!(p.flags, "");
|
||||
assert_eq!(p.quotes, "\"\"\"");
|
||||
let p = get_prefix("t\"hello\"");
|
||||
assert_eq!(p.flags, "t");
|
||||
assert_eq!(p.quotes, "\"");
|
||||
let p = get_prefix("Tr'world'");
|
||||
assert_eq!(p.flags, "Tr");
|
||||
assert_eq!(p.quotes, "'");
|
||||
}
|
||||
|
||||
fn get_string_contents(s: String) -> String {
|
||||
@@ -227,6 +240,10 @@ pub mod extra_functions {
|
||||
assert_eq!(get_string_contents(s.to_owned()), "");
|
||||
let s = "''''''";
|
||||
assert_eq!(get_string_contents(s.to_owned()), "");
|
||||
let s = "t\"tmpl\"";
|
||||
assert_eq!(get_string_contents(s.to_owned()), "tmpl");
|
||||
let s = "Tr'world'";
|
||||
assert_eq!(get_string_contents(s.to_owned()), "world");
|
||||
}
|
||||
|
||||
pub struct StringPrefix;
|
||||
@@ -291,7 +308,11 @@ pub mod extra_functions {
|
||||
let node = graph[parameters.param()?.into_syntax_node_ref()?];
|
||||
parameters.finish()?;
|
||||
let prefix = get_prefix(&source[node.byte_range()]).full();
|
||||
let prefix = prefix.replace("f", "").replace("F", "");
|
||||
let prefix = prefix
|
||||
.replace("f", "")
|
||||
.replace("F", "")
|
||||
.replace("t", "")
|
||||
.replace("T", "");
|
||||
Ok(Value::String(prefix))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ module.exports = grammar({
|
||||
$._string_start,
|
||||
$._string_content,
|
||||
$._string_end,
|
||||
$._template_string_start,
|
||||
],
|
||||
|
||||
inline: $ => [
|
||||
@@ -423,6 +424,8 @@ module.exports = grammar({
|
||||
),
|
||||
$.string,
|
||||
$.concatenated_string,
|
||||
$.template_string,
|
||||
$.concatenated_template_string,
|
||||
$.none,
|
||||
$.true,
|
||||
$.false
|
||||
@@ -765,6 +768,8 @@ module.exports = grammar({
|
||||
$.keyword_identifier,
|
||||
$.string,
|
||||
$.concatenated_string,
|
||||
$.template_string,
|
||||
$.concatenated_template_string,
|
||||
$.integer,
|
||||
$.float,
|
||||
$.true,
|
||||
@@ -1099,6 +1104,20 @@ module.exports = grammar({
|
||||
field('suffix', alias($._string_end, '"'))
|
||||
),
|
||||
|
||||
concatenated_template_string: $ => seq(
|
||||
$.template_string,
|
||||
repeat1($.template_string)
|
||||
),
|
||||
|
||||
template_string: $ => seq(
|
||||
field('prefix', alias($._template_string_start, '"')),
|
||||
repeat(choice(
|
||||
field('interpolation', $.interpolation),
|
||||
field('string_content', $.string_content)
|
||||
)),
|
||||
field('suffix', alias($._string_end, '"'))
|
||||
),
|
||||
|
||||
string_content: $ => prec.right(0, repeat1(
|
||||
choice(
|
||||
$._escape_interpolation,
|
||||
|
||||
@@ -1800,6 +1800,14 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "concatenated_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "template_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "concatenated_template_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "none"
|
||||
@@ -3891,6 +3899,14 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "concatenated_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "template_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "concatenated_template_string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "integer"
|
||||
@@ -5982,6 +5998,77 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"concatenated_template_string": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "template_string"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT1",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "template_string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"template_string": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "prefix",
|
||||
"content": {
|
||||
"type": "ALIAS",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_template_string_start"
|
||||
},
|
||||
"named": false,
|
||||
"value": "\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "interpolation",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "interpolation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "string_content",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "string_content"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "suffix",
|
||||
"content": {
|
||||
"type": "ALIAS",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_string_end"
|
||||
},
|
||||
"named": false,
|
||||
"value": "\""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"string_content": {
|
||||
"type": "PREC_RIGHT",
|
||||
"value": 0,
|
||||
@@ -6710,6 +6797,10 @@
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_string_end"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_template_string_start"
|
||||
}
|
||||
],
|
||||
"inline": [
|
||||
|
||||
@@ -241,6 +241,10 @@
|
||||
"type": "concatenated_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "concatenated_template_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "dictionary",
|
||||
"named": true
|
||||
@@ -305,6 +309,10 @@
|
||||
"type": "subscript",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "template_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "true",
|
||||
"named": true
|
||||
@@ -1000,6 +1008,21 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "concatenated_template_string",
|
||||
"named": true,
|
||||
"fields": {},
|
||||
"children": {
|
||||
"multiple": true,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "template_string",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "conditional_expression",
|
||||
"named": true,
|
||||
@@ -2460,6 +2483,10 @@
|
||||
"type": "concatenated_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "concatenated_template_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "false",
|
||||
"named": true
|
||||
@@ -2472,6 +2499,10 @@
|
||||
"type": "string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "template_string",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "true",
|
||||
"named": true
|
||||
@@ -3257,6 +3288,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "template_string",
|
||||
"named": true,
|
||||
"fields": {
|
||||
"interpolation": {
|
||||
"multiple": true,
|
||||
"required": false,
|
||||
"types": [
|
||||
{
|
||||
"type": "interpolation",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"prefix": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "\"",
|
||||
"named": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"string_content": {
|
||||
"multiple": true,
|
||||
"required": false,
|
||||
"types": [
|
||||
{
|
||||
"type": "string_content",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"suffix": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "\"",
|
||||
"named": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "try_statement",
|
||||
"named": true,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ enum TokenType {
|
||||
STRING_START,
|
||||
STRING_CONTENT,
|
||||
STRING_END,
|
||||
TEMPLATE_STRING_START,
|
||||
};
|
||||
|
||||
struct Delimiter {
|
||||
@@ -28,6 +29,7 @@ struct Delimiter {
|
||||
Format = 1 << 4,
|
||||
Triple = 1 << 5,
|
||||
Bytes = 1 << 6,
|
||||
Template = 1 << 7,
|
||||
};
|
||||
|
||||
Delimiter() : flags(0) {}
|
||||
@@ -36,6 +38,14 @@ struct Delimiter {
|
||||
return flags & Format;
|
||||
}
|
||||
|
||||
bool is_template() const {
|
||||
return flags & Template;
|
||||
}
|
||||
|
||||
bool can_interpolate() const {
|
||||
return is_format() || is_template();
|
||||
}
|
||||
|
||||
bool is_raw() const {
|
||||
return flags & Raw;
|
||||
}
|
||||
@@ -59,6 +69,10 @@ struct Delimiter {
|
||||
flags |= Format;
|
||||
}
|
||||
|
||||
void set_template() {
|
||||
flags |= Template;
|
||||
}
|
||||
|
||||
void set_raw() {
|
||||
flags |= Raw;
|
||||
}
|
||||
@@ -154,7 +168,7 @@ struct Scanner {
|
||||
int32_t end_character = delimiter.end_character();
|
||||
bool has_content = false;
|
||||
while (lexer->lookahead) {
|
||||
if ((lexer->lookahead == '{' || lexer->lookahead == '}') && delimiter.is_format()) {
|
||||
if ((lexer->lookahead == '{' || lexer->lookahead == '}') && delimiter.can_interpolate()) {
|
||||
lexer->mark_end(lexer);
|
||||
lexer->result_symbol = STRING_CONTENT;
|
||||
return has_content;
|
||||
@@ -322,13 +336,17 @@ struct Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
if (first_comment_indent_length == -1 && valid_symbols[STRING_START]) {
|
||||
bool expects_string_start = valid_symbols[STRING_START] || valid_symbols[TEMPLATE_STRING_START];
|
||||
|
||||
if (first_comment_indent_length == -1 && expects_string_start) {
|
||||
Delimiter delimiter;
|
||||
|
||||
bool has_flags = false;
|
||||
while (lexer->lookahead) {
|
||||
if (lexer->lookahead == 'f' || lexer->lookahead == 'F') {
|
||||
delimiter.set_format();
|
||||
} else if (lexer->lookahead == 't' || lexer->lookahead == 'T') {
|
||||
delimiter.set_template();
|
||||
} else if (lexer->lookahead == 'r' || lexer->lookahead == 'R') {
|
||||
delimiter.set_raw();
|
||||
} else if (lexer->lookahead == 'b' || lexer->lookahead == 'B') {
|
||||
@@ -372,7 +390,7 @@ struct Scanner {
|
||||
|
||||
if (delimiter.end_character()) {
|
||||
delimiter_stack.push_back(delimiter);
|
||||
lexer->result_symbol = STRING_START;
|
||||
lexer->result_symbol = delimiter.is_template() ? TEMPLATE_STRING_START : STRING_START;
|
||||
return true;
|
||||
} else if (has_flags) {
|
||||
return false;
|
||||
|
||||
@@ -123,7 +123,6 @@ struct TSLanguage {
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* The Python extractor now supports template strings as defined in [PEP-750](https://peps.python.org/pep-0750/), through the classes `TemplateString` and `JoinedTemplateString`.
|
||||
@@ -218,6 +218,9 @@ class DictItemListParent extends DictItemListParent_ { }
|
||||
/** A list of strings (the primitive type string not Bytes or Unicode) */
|
||||
class StringList extends StringList_ { }
|
||||
|
||||
/** A list of template strings. */
|
||||
class TemplateStringList extends TemplateStringList_ { }
|
||||
|
||||
/** A list of aliases in an import statement */
|
||||
class AliasList extends AliasList_ { }
|
||||
|
||||
@@ -273,3 +276,9 @@ class ParamSpec extends ParamSpec_, TypeParameter {
|
||||
|
||||
override Expr getAChildNode() { result = this.getName() }
|
||||
}
|
||||
|
||||
/** A template string literal. */
|
||||
class TemplateString extends TemplateString_, Expr { }
|
||||
|
||||
/** An (implicitly) concatenated list of template strings. */
|
||||
class JoinedTemplateString extends JoinedTemplateString_, Expr { }
|
||||
|
||||
@@ -768,6 +768,20 @@ class Fstring_ extends @py_Fstring, Expr {
|
||||
override string toString() { result = "Fstring" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `JoinedTemplateString` for further information. */
|
||||
class JoinedTemplateString_ extends @py_JoinedTemplateString, Expr {
|
||||
/** Gets the strings of this joined template string. */
|
||||
TemplateStringList getStrings() { py_TemplateString_lists(result, this) }
|
||||
|
||||
/** Gets the nth string of this joined template string. */
|
||||
TemplateString getString(int index) { result = this.getStrings().getItem(index) }
|
||||
|
||||
/** Gets a string of this joined template string. */
|
||||
TemplateString getAString() { result = this.getStrings().getAnItem() }
|
||||
|
||||
override string toString() { result = "JoinedTemplateString" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `KeyValuePair` for further information. */
|
||||
class KeyValuePair_ extends @py_KeyValuePair, DictItem {
|
||||
/** Gets the location of this key-value pair. */
|
||||
@@ -1373,6 +1387,48 @@ class TemplateDottedNotation_ extends @py_TemplateDottedNotation, Expr {
|
||||
override string toString() { result = "TemplateDottedNotation" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `TemplateString` for further information. */
|
||||
class TemplateString_ extends @py_TemplateString, Expr {
|
||||
/** Gets the prefix of this template string literal. */
|
||||
string getPrefix() { py_strs(result, this, 2) }
|
||||
|
||||
/** Gets the values of this template string literal. */
|
||||
ExprList getValues() { py_expr_lists(result, this, 3) }
|
||||
|
||||
/** Gets the nth value of this template string literal. */
|
||||
Expr getValue(int index) { result = this.getValues().getItem(index) }
|
||||
|
||||
/** Gets a value of this template string literal. */
|
||||
Expr getAValue() { result = this.getValues().getAnItem() }
|
||||
|
||||
override ExprParent getParent() { py_exprs(this, _, result, _) }
|
||||
|
||||
override string toString() { result = "TemplateString" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `TemplateStringPart` for further information. */
|
||||
class TemplateStringPart_ extends @py_TemplateStringPart, Expr {
|
||||
/** Gets the text of this string part of a template string. */
|
||||
string getText() { py_strs(result, this, 2) }
|
||||
|
||||
override string toString() { result = "TemplateStringPart" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `TemplateStringList` for further information. */
|
||||
class TemplateStringList_ extends @py_TemplateString_list {
|
||||
/** Gets a parent of this template string literal list */
|
||||
JoinedTemplateString getParent() { py_TemplateString_lists(this, result) }
|
||||
|
||||
/** Gets an item of this template string literal list */
|
||||
Expr getAnItem() { py_exprs(result, _, this, _) }
|
||||
|
||||
/** Gets the nth item of this template string literal list */
|
||||
Expr getItem(int index) { py_exprs(result, _, this, index) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "TemplateStringList" }
|
||||
}
|
||||
|
||||
/** INTERNAL: See the class `TemplateWrite` for further information. */
|
||||
class TemplateWrite_ extends @py_TemplateWrite, Stmt {
|
||||
/** Gets the value of this template write statement. */
|
||||
|
||||
@@ -715,7 +715,7 @@ private module InterModulePointsTo {
|
||||
i.getImportedModuleName() = name and
|
||||
PointsToInternal::module_imported_as(value, name) and
|
||||
origin = f and
|
||||
context.appliesTo(f)
|
||||
context.appliesTo(pragma[only_bind_into](f))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -530,6 +530,10 @@ py_extracted_version(int module : @py_Module ref,
|
||||
/* <Field> Fstring.values = 2, expr_list */
|
||||
/* <Parent> Fstring = FormattedValue */
|
||||
|
||||
/* <Field> JoinedTemplateString.location = 0, location */
|
||||
/* <Field> JoinedTemplateString.parenthesised = 1, bool */
|
||||
/* <Field> JoinedTemplateString.strings = 2, TemplateString_list */
|
||||
|
||||
/* <Field> KeyValuePair.location = 0, location */
|
||||
/* <Field> KeyValuePair.value = 1, expr */
|
||||
/* <Field> KeyValuePair.key = 2, expr */
|
||||
@@ -709,6 +713,17 @@ py_extracted_version(int module : @py_Module ref,
|
||||
/* <Field> TemplateDottedNotation.attr = 3, str */
|
||||
/* <Field> TemplateDottedNotation.ctx = 4, expr_context */
|
||||
|
||||
/* <Field> TemplateString.location = 0, location */
|
||||
/* <Field> TemplateString.parenthesised = 1, bool */
|
||||
/* <Field> TemplateString.prefix = 2, str */
|
||||
/* <Field> TemplateString.values = 3, expr_list */
|
||||
/* <Parent> TemplateString = TemplateStringList */
|
||||
|
||||
/* <Field> TemplateStringPart.location = 0, location */
|
||||
/* <Field> TemplateStringPart.parenthesised = 1, bool */
|
||||
/* <Field> TemplateStringPart.text = 2, str */
|
||||
/* <Parent> TemplateStringList = JoinedTemplateString */
|
||||
|
||||
/* <Field> TemplateWrite.location = 0, location */
|
||||
/* <Field> TemplateWrite.value = 1, expr */
|
||||
|
||||
@@ -835,6 +850,9 @@ py_StringParts(unique int id : @py_StringPart,
|
||||
py_StringPart_lists(unique int id : @py_StringPart_list,
|
||||
unique int parent : @py_Bytes_or_Str ref);
|
||||
|
||||
py_TemplateString_lists(unique int id : @py_TemplateString_list,
|
||||
unique int parent : @py_JoinedTemplateString ref);
|
||||
|
||||
py_aliases(unique int id : @py_alias,
|
||||
int parent : @py_alias_list ref,
|
||||
int idx : int ref);
|
||||
@@ -1010,7 +1028,10 @@ case @py_expr.kind of
|
||||
| 36 = @py_Fstring
|
||||
| 37 = @py_FormattedValue
|
||||
| 38 = @py_AssignExpr
|
||||
| 39 = @py_SpecialOperation;
|
||||
| 39 = @py_SpecialOperation
|
||||
| 40 = @py_TemplateString
|
||||
| 41 = @py_JoinedTemplateString
|
||||
| 42 = @py_TemplateStringPart;
|
||||
|
||||
case @py_expr_context.kind of
|
||||
0 = @py_AugLoad
|
||||
@@ -1105,11 +1126,11 @@ case @py_unaryop.kind of
|
||||
|
||||
@py_expr_context_parent = @py_Attribute | @py_List | @py_Name | @py_PlaceHolder | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_Tuple;
|
||||
|
||||
@py_expr_list_parent = @py_Assign | @py_BoolExpr | @py_Call | @py_ClassExpr | @py_Compare | @py_Delete | @py_Fstring | @py_Function | @py_List | @py_Print | @py_Set | @py_SpecialOperation | @py_Tuple | @py_arguments | @py_comprehension;
|
||||
@py_expr_list_parent = @py_Assign | @py_BoolExpr | @py_Call | @py_ClassExpr | @py_Compare | @py_Delete | @py_Fstring | @py_Function | @py_List | @py_Print | @py_Set | @py_SpecialOperation | @py_TemplateString | @py_Tuple | @py_arguments | @py_comprehension;
|
||||
|
||||
@py_expr_or_stmt = @py_expr | @py_stmt;
|
||||
|
||||
@py_expr_parent = @py_AnnAssign | @py_Assert | @py_Assign | @py_AssignExpr | @py_Attribute | @py_AugAssign | @py_Await | @py_BinaryExpr | @py_Call | @py_Case | @py_Compare | @py_DictComp | @py_DictUnpacking | @py_ExceptGroupStmt | @py_ExceptStmt | @py_Exec | @py_Expr_stmt | @py_Filter | @py_For | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_GeneratorExp | @py_Guard | @py_If | @py_IfExp | @py_ImportMember | @py_ImportStar | @py_KeyValuePair | @py_ListComp | @py_MatchAsPattern | @py_MatchCapturePattern | @py_MatchClassPattern | @py_MatchKeywordPattern | @py_MatchLiteralPattern | @py_MatchStmt | @py_MatchValuePattern | @py_ParamSpec | @py_Print | @py_Raise | @py_Repr | @py_Return | @py_SetComp | @py_Slice | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_TemplateWrite | @py_TypeAlias | @py_TypeVar | @py_TypeVarTuple | @py_UnaryExpr | @py_While | @py_With | @py_Yield | @py_YieldFrom | @py_alias | @py_arguments | @py_comprehension | @py_expr_list | @py_keyword | @py_parameter_list;
|
||||
@py_expr_parent = @py_AnnAssign | @py_Assert | @py_Assign | @py_AssignExpr | @py_Attribute | @py_AugAssign | @py_Await | @py_BinaryExpr | @py_Call | @py_Case | @py_Compare | @py_DictComp | @py_DictUnpacking | @py_ExceptGroupStmt | @py_ExceptStmt | @py_Exec | @py_Expr_stmt | @py_Filter | @py_For | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_GeneratorExp | @py_Guard | @py_If | @py_IfExp | @py_ImportMember | @py_ImportStar | @py_KeyValuePair | @py_ListComp | @py_MatchAsPattern | @py_MatchCapturePattern | @py_MatchClassPattern | @py_MatchKeywordPattern | @py_MatchLiteralPattern | @py_MatchStmt | @py_MatchValuePattern | @py_ParamSpec | @py_Print | @py_Raise | @py_Repr | @py_Return | @py_SetComp | @py_Slice | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_TemplateString_list | @py_TemplateWrite | @py_TypeAlias | @py_TypeVar | @py_TypeVarTuple | @py_UnaryExpr | @py_While | @py_With | @py_Yield | @py_YieldFrom | @py_alias | @py_arguments | @py_comprehension | @py_expr_list | @py_keyword | @py_parameter_list;
|
||||
|
||||
@py_location_parent = @py_DictUnpacking | @py_KeyValuePair | @py_StringPart | @py_comprehension | @py_expr | @py_keyword | @py_pattern | @py_stmt | @py_type_parameter;
|
||||
|
||||
@@ -1125,7 +1146,7 @@ case @py_unaryop.kind of
|
||||
|
||||
@py_str_list_parent = @py_Global | @py_Nonlocal;
|
||||
|
||||
@py_str_parent = @py_Attribute | @py_Class | @py_ClassExpr | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_ImportExpr | @py_ImportMember | @py_Module | @py_SpecialOperation | @py_Str | @py_StringPart | @py_TemplateDottedNotation | @py_keyword | @py_str_list;
|
||||
@py_str_parent = @py_Attribute | @py_Class | @py_ClassExpr | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_ImportExpr | @py_ImportMember | @py_Module | @py_SpecialOperation | @py_Str | @py_StringPart | @py_TemplateDottedNotation | @py_TemplateString | @py_TemplateStringPart | @py_keyword | @py_str_list;
|
||||
|
||||
@py_type_parameter_list_parent = @py_ClassExpr | @py_Function | @py_TypeAlias;
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<k>@py_TypeVar</k><v>100</v></e><e>
|
||||
<k>@py_TypeVarTuple</k><v>100</v></e><e>
|
||||
<k>@py_type_parameter_list</k><v>100</v></e><e>
|
||||
<k>@py_TemplateStringPart</k><v>100</v></e><e>
|
||||
<k>@py_TemplateString_list</k><v>100</v></e><e>
|
||||
<k>@py_JoinedTemplateString</k><v>100</v></e><e>
|
||||
<k>@py_TemplateString</k><v>100</v></e><e>
|
||||
<k>@py_Guard</k><v>100</v></e><e>
|
||||
<k>@py_MatchAsPattern</k><v>100</v></e><e>
|
||||
<k>@py_MatchOrPattern</k><v>100</v></e><e>
|
||||
@@ -7959,6 +7963,21 @@
|
||||
</dependencies>
|
||||
</relation>
|
||||
<relation>
|
||||
<name>py_TemplateString_lists</name>
|
||||
<cardinality>1000</cardinality>
|
||||
<columnsizes>
|
||||
<e>
|
||||
<k>id</k>
|
||||
<v>1000</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>parent</k>
|
||||
<v>1000</v>
|
||||
</e>
|
||||
</columnsizes>
|
||||
<dependencies/>
|
||||
</relation>
|
||||
<relation>
|
||||
<name>py_aliases</name>
|
||||
<cardinality>21374</cardinality>
|
||||
<columnsizes>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Add support for template string literals
|
||||
compatibility: backwards
|
||||
Reference in New Issue
Block a user