Tagged template literal AST (#5185)
* tagged template literal ast * add comment
This commit is contained in:
parent
7466c81414
commit
730a4bcdad
|
@ -1353,6 +1353,30 @@
|
||||||
return unquoted;
|
return unquoted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `StringLiteral`s can represent either entire literal strings
|
||||||
|
// or pieces of text inside of e.g. an interpolated string.
|
||||||
|
// When parsed as the former but needing to be treated as the latter
|
||||||
|
// (e.g. the string part of a tagged template literal), this will return
|
||||||
|
// a copy of the `StringLiteral` with the quotes trimmed from its location
|
||||||
|
// data (like it would have if parsed as part of an interpolated string).
|
||||||
|
withoutQuotesInLocationData() {
|
||||||
|
var copy, endsWithNewline, locationData;
|
||||||
|
endsWithNewline = this.originalValue.slice(-1) === '\n';
|
||||||
|
locationData = Object.assign({}, this.locationData);
|
||||||
|
locationData.first_column += this.quote.length;
|
||||||
|
if (endsWithNewline) {
|
||||||
|
locationData.last_line -= 1;
|
||||||
|
locationData.last_column = locationData.last_line === locationData.first_line ? locationData.first_column + this.originalValue.length - '\n'.length : this.originalValue.slice(0, -1).length - '\n'.length - this.originalValue.slice(0, -1).lastIndexOf('\n');
|
||||||
|
} else {
|
||||||
|
locationData.last_column -= this.quote.length;
|
||||||
|
}
|
||||||
|
locationData.last_column_exclusive -= this.quote.length;
|
||||||
|
locationData.range = [locationData.range[0] + this.quote.length, locationData.range[1] - this.quote.length];
|
||||||
|
copy = new StringLiteral(this.originalValue, {quote: this.quote, initialChunk: this.initialChunk, finalChunk: this.finalChunk, indent: this.indent, double: this.double, heregex: this.heregex});
|
||||||
|
copy.locationData = locationData;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
astProperties() {
|
astProperties() {
|
||||||
return {
|
return {
|
||||||
value: this.originalValue,
|
value: this.originalValue,
|
||||||
|
@ -2831,7 +2855,7 @@
|
||||||
exports.TaggedTemplateCall = TaggedTemplateCall = class TaggedTemplateCall extends Call {
|
exports.TaggedTemplateCall = TaggedTemplateCall = class TaggedTemplateCall extends Call {
|
||||||
constructor(variable, arg, soak) {
|
constructor(variable, arg, soak) {
|
||||||
if (arg instanceof StringLiteral) {
|
if (arg instanceof StringLiteral) {
|
||||||
arg = new StringWithInterpolations(Block.wrap([new Value(arg)]));
|
arg = StringWithInterpolations.fromStringLiteral(arg);
|
||||||
}
|
}
|
||||||
super(variable, [arg], soak);
|
super(variable, [arg], soak);
|
||||||
}
|
}
|
||||||
|
@ -2840,6 +2864,17 @@
|
||||||
return this.variable.compileToFragments(o, LEVEL_ACCESS).concat(this.args[0].compileToFragments(o, LEVEL_LIST));
|
return this.variable.compileToFragments(o, LEVEL_ACCESS).concat(this.args[0].compileToFragments(o, LEVEL_LIST));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
astType() {
|
||||||
|
return 'TaggedTemplateExpression';
|
||||||
|
}
|
||||||
|
|
||||||
|
astProperties(o) {
|
||||||
|
return {
|
||||||
|
tag: this.variable.ast(o, LEVEL_ACCESS),
|
||||||
|
quasi: this.args[0].ast(o, LEVEL_LIST)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//### Extends
|
//### Extends
|
||||||
|
@ -6699,6 +6734,15 @@
|
||||||
this.startQuote = startQuote;
|
this.startQuote = startQuote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromStringLiteral(stringLiteral) {
|
||||||
|
var updatedString, updatedStringValue;
|
||||||
|
updatedString = stringLiteral.withoutQuotesInLocationData();
|
||||||
|
updatedStringValue = new Value(updatedString).withLocationDataFrom(updatedString);
|
||||||
|
return new StringWithInterpolations(Block.wrap([updatedStringValue]), {
|
||||||
|
quote: stringLiteral.quote
|
||||||
|
}).withLocationDataFrom(stringLiteral);
|
||||||
|
}
|
||||||
|
|
||||||
// `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
|
// `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
|
||||||
// and using @body.compileNode. `StringWithInterpolations.compileNode` is
|
// and using @body.compileNode. `StringWithInterpolations.compileNode` is
|
||||||
// _the_ custom logic to output interpolated strings as code.
|
// _the_ custom logic to output interpolated strings as code.
|
||||||
|
|
|
@ -949,6 +949,34 @@ exports.StringLiteral = class StringLiteral extends Literal
|
||||||
unquoted = unquoted.replace /\\n/g, '\n' if csx
|
unquoted = unquoted.replace /\\n/g, '\n' if csx
|
||||||
unquoted
|
unquoted
|
||||||
|
|
||||||
|
# `StringLiteral`s can represent either entire literal strings
|
||||||
|
# or pieces of text inside of e.g. an interpolated string.
|
||||||
|
# When parsed as the former but needing to be treated as the latter
|
||||||
|
# (e.g. the string part of a tagged template literal), this will return
|
||||||
|
# a copy of the `StringLiteral` with the quotes trimmed from its location
|
||||||
|
# data (like it would have if parsed as part of an interpolated string).
|
||||||
|
withoutQuotesInLocationData: ->
|
||||||
|
endsWithNewline = @originalValue[-1..] is '\n'
|
||||||
|
locationData = Object.assign {}, @locationData
|
||||||
|
locationData.first_column += @quote.length
|
||||||
|
if endsWithNewline
|
||||||
|
locationData.last_line -= 1
|
||||||
|
locationData.last_column =
|
||||||
|
if locationData.last_line is locationData.first_line
|
||||||
|
locationData.first_column + @originalValue.length - '\n'.length
|
||||||
|
else
|
||||||
|
@originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
|
||||||
|
else
|
||||||
|
locationData.last_column -= @quote.length
|
||||||
|
locationData.last_column_exclusive -= @quote.length
|
||||||
|
locationData.range = [
|
||||||
|
locationData.range[0] + @quote.length
|
||||||
|
locationData.range[1] - @quote.length
|
||||||
|
]
|
||||||
|
copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
|
||||||
|
copy.locationData = locationData
|
||||||
|
copy
|
||||||
|
|
||||||
astProperties: ->
|
astProperties: ->
|
||||||
return
|
return
|
||||||
value: @originalValue
|
value: @originalValue
|
||||||
|
@ -1906,12 +1934,19 @@ exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
|
||||||
|
|
||||||
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
|
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
|
||||||
constructor: (variable, arg, soak) ->
|
constructor: (variable, arg, soak) ->
|
||||||
arg = new StringWithInterpolations Block.wrap([ new Value arg ]) if arg instanceof StringLiteral
|
arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
|
||||||
super variable, [ arg ], soak
|
super variable, [ arg ], soak
|
||||||
|
|
||||||
compileNode: (o) ->
|
compileNode: (o) ->
|
||||||
@variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
|
@variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
|
||||||
|
|
||||||
|
astType: -> 'TaggedTemplateExpression'
|
||||||
|
|
||||||
|
astProperties: (o) ->
|
||||||
|
return
|
||||||
|
tag: @variable.ast o, LEVEL_ACCESS
|
||||||
|
quasi: @args[0].ast o, LEVEL_LIST
|
||||||
|
|
||||||
#### Extends
|
#### Extends
|
||||||
|
|
||||||
# Node to extend an object's prototype with an ancestor object.
|
# Node to extend an object's prototype with an ancestor object.
|
||||||
|
@ -4469,6 +4504,12 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base
|
||||||
constructor: (@body, {@quote, @startQuote} = {}) ->
|
constructor: (@body, {@quote, @startQuote} = {}) ->
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@fromStringLiteral: (stringLiteral) ->
|
||||||
|
updatedString = stringLiteral.withoutQuotesInLocationData()
|
||||||
|
updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
|
||||||
|
new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote
|
||||||
|
.withLocationDataFrom stringLiteral
|
||||||
|
|
||||||
children: ['body']
|
children: ['body']
|
||||||
|
|
||||||
# `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
|
# `unwrap` returns `this` to stop ancestor nodes reaching in to grab @body,
|
||||||
|
|
|
@ -670,12 +670,80 @@ test "AST as expected for RegexWithInterpolations node", ->
|
||||||
quote: '///'
|
quote: '///'
|
||||||
flags: 'ig'
|
flags: 'ig'
|
||||||
|
|
||||||
# test "AST as expected for TaggedTemplateCall node", ->
|
test "AST as expected for TaggedTemplateCall node", ->
|
||||||
# testExpression 'func"tagged"',
|
testExpression 'func"tagged"',
|
||||||
# type: 'TaggedTemplateCall'
|
type: 'TaggedTemplateExpression'
|
||||||
# args: [
|
tag: ID 'func'
|
||||||
# type: 'StringWithInterpolations'
|
quasi:
|
||||||
# ]
|
type: 'TemplateLiteral'
|
||||||
|
expressions: []
|
||||||
|
quasis: [
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: 'tagged'
|
||||||
|
tail: yes
|
||||||
|
]
|
||||||
|
|
||||||
|
testExpression 'a"b#{c}"',
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag: ID 'a'
|
||||||
|
quasi:
|
||||||
|
type: 'TemplateLiteral'
|
||||||
|
expressions: [
|
||||||
|
ID 'c'
|
||||||
|
]
|
||||||
|
quasis: [
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: 'b'
|
||||||
|
tail: no
|
||||||
|
,
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: ''
|
||||||
|
tail: yes
|
||||||
|
]
|
||||||
|
|
||||||
|
testExpression '''
|
||||||
|
a"""
|
||||||
|
b#{c}
|
||||||
|
"""
|
||||||
|
''',
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag: ID 'a'
|
||||||
|
quasi:
|
||||||
|
type: 'TemplateLiteral'
|
||||||
|
expressions: [
|
||||||
|
ID 'c'
|
||||||
|
]
|
||||||
|
quasis: [
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: '\n b'
|
||||||
|
tail: no
|
||||||
|
,
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: '\n'
|
||||||
|
tail: yes
|
||||||
|
]
|
||||||
|
|
||||||
|
testExpression """
|
||||||
|
a'''
|
||||||
|
b
|
||||||
|
'''
|
||||||
|
""",
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag: ID 'a'
|
||||||
|
quasi:
|
||||||
|
type: 'TemplateLiteral'
|
||||||
|
expressions: []
|
||||||
|
quasis: [
|
||||||
|
type: 'TemplateElement'
|
||||||
|
value:
|
||||||
|
raw: '\n b\n'
|
||||||
|
tail: yes
|
||||||
|
]
|
||||||
|
|
||||||
# test "AST as expected for Extends node", ->
|
# test "AST as expected for Extends node", ->
|
||||||
# testExpression 'class child extends parent',
|
# testExpression 'class child extends parent',
|
||||||
|
|
|
@ -5872,3 +5872,246 @@ test "AST location data as expected for RegexLiteral node", ->
|
||||||
end:
|
end:
|
||||||
line: 5
|
line: 5
|
||||||
column: 3
|
column: 3
|
||||||
|
|
||||||
|
test "AST as expected for TaggedTemplateCall node", ->
|
||||||
|
testAstLocationData 'func"tagged"',
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag:
|
||||||
|
start: 0
|
||||||
|
end: 4
|
||||||
|
range: [0, 4]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 4
|
||||||
|
quasi:
|
||||||
|
quasis: [
|
||||||
|
start: 5
|
||||||
|
end: 11
|
||||||
|
range: [5, 11]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 5
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 11
|
||||||
|
]
|
||||||
|
start: 4
|
||||||
|
end: 12
|
||||||
|
range: [4, 12]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 4
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 12
|
||||||
|
start: 0
|
||||||
|
end: 12
|
||||||
|
range: [0, 12]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 12
|
||||||
|
|
||||||
|
testAstLocationData 'a"b#{c}"',
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag:
|
||||||
|
start: 0
|
||||||
|
end: 1
|
||||||
|
range: [0, 1]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
quasi:
|
||||||
|
expressions: [
|
||||||
|
start: 5
|
||||||
|
end: 6
|
||||||
|
range: [5, 6]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 5
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 6
|
||||||
|
]
|
||||||
|
quasis: [
|
||||||
|
start: 2
|
||||||
|
end: 3
|
||||||
|
range: [2, 3]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 2
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 3
|
||||||
|
,
|
||||||
|
start: 7
|
||||||
|
end: 7
|
||||||
|
range: [7, 7]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 7
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 7
|
||||||
|
]
|
||||||
|
start: 1
|
||||||
|
end: 8
|
||||||
|
range: [1, 8]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 8
|
||||||
|
start: 0
|
||||||
|
end: 8
|
||||||
|
range: [0, 8]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 8
|
||||||
|
|
||||||
|
testAstLocationData '''
|
||||||
|
a"""
|
||||||
|
b#{c}
|
||||||
|
"""
|
||||||
|
''',
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag:
|
||||||
|
start: 0
|
||||||
|
end: 1
|
||||||
|
range: [0, 1]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
quasi:
|
||||||
|
expressions: [
|
||||||
|
start: 10
|
||||||
|
end: 11
|
||||||
|
range: [10, 11]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 2
|
||||||
|
column: 5
|
||||||
|
end:
|
||||||
|
line: 2
|
||||||
|
column: 6
|
||||||
|
]
|
||||||
|
quasis: [
|
||||||
|
start: 4
|
||||||
|
end: 8
|
||||||
|
range: [4, 8]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 4
|
||||||
|
end:
|
||||||
|
line: 2
|
||||||
|
column: 3
|
||||||
|
,
|
||||||
|
start: 12
|
||||||
|
end: 13
|
||||||
|
range: [12, 13]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 2
|
||||||
|
column: 7
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 0
|
||||||
|
]
|
||||||
|
start: 1
|
||||||
|
end: 16
|
||||||
|
range: [1, 16]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 3
|
||||||
|
start: 0
|
||||||
|
end: 16
|
||||||
|
range: [0, 16]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 3
|
||||||
|
|
||||||
|
testAstLocationData """
|
||||||
|
a'''
|
||||||
|
b
|
||||||
|
'''
|
||||||
|
""",
|
||||||
|
type: 'TaggedTemplateExpression'
|
||||||
|
tag:
|
||||||
|
start: 0
|
||||||
|
end: 1
|
||||||
|
range: [0, 1]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
quasi:
|
||||||
|
quasis: [
|
||||||
|
start: 4
|
||||||
|
end: 9
|
||||||
|
range: [4, 9]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 4
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 0
|
||||||
|
]
|
||||||
|
start: 1
|
||||||
|
end: 12
|
||||||
|
range: [1, 12]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 1
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 3
|
||||||
|
start: 0
|
||||||
|
end: 12
|
||||||
|
range: [0, 12]
|
||||||
|
loc:
|
||||||
|
start:
|
||||||
|
line: 1
|
||||||
|
column: 0
|
||||||
|
end:
|
||||||
|
line: 3
|
||||||
|
column: 3
|
||||||
|
|
|
@ -651,3 +651,72 @@ test 'Values with properties end up with a location that includes the properties
|
||||||
eq complexIndex.locationData.first_column, 0
|
eq complexIndex.locationData.first_column, 0
|
||||||
eq complexIndex.locationData.last_line, 3
|
eq complexIndex.locationData.last_line, 3
|
||||||
eq complexIndex.locationData.last_column, 9
|
eq complexIndex.locationData.last_column, 9
|
||||||
|
|
||||||
|
test 'StringWithInterpolations::fromStringLiteral() assigns correct location to tagged template literal', ->
|
||||||
|
checkLocationData = (source, {stringWithInterpolationsLocationData, stringLocationData}) ->
|
||||||
|
block = CoffeeScript.nodes source
|
||||||
|
taggedTemplateLiteral = block.expressions[0].unwrap()
|
||||||
|
{args: [stringWithInterpolations]} = taggedTemplateLiteral
|
||||||
|
{body} = stringWithInterpolations
|
||||||
|
{expressions: [stringValue]} = body
|
||||||
|
string = stringValue.unwrap()
|
||||||
|
|
||||||
|
for field in ['first_line', 'first_column', 'last_line', 'last_column', 'last_line_exclusive', 'last_column_exclusive']
|
||||||
|
eq stringWithInterpolations.locationData[field], stringWithInterpolationsLocationData[field]
|
||||||
|
eq stringValue.locationData[field], stringLocationData[field]
|
||||||
|
eq string.locationData[field], stringLocationData[field]
|
||||||
|
|
||||||
|
checkLocationData 'a"b"',
|
||||||
|
stringWithInterpolationsLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 1
|
||||||
|
last_line: 0
|
||||||
|
last_column: 3
|
||||||
|
last_line_exclusive: 0
|
||||||
|
last_column_exclusive: 4
|
||||||
|
stringLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 2
|
||||||
|
last_line: 0
|
||||||
|
last_column: 2
|
||||||
|
last_line_exclusive: 0
|
||||||
|
last_column_exclusive: 3
|
||||||
|
|
||||||
|
checkLocationData '''
|
||||||
|
a"""
|
||||||
|
b
|
||||||
|
"""
|
||||||
|
''',
|
||||||
|
stringWithInterpolationsLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 1
|
||||||
|
last_line: 2
|
||||||
|
last_column: 2
|
||||||
|
last_line_exclusive: 2
|
||||||
|
last_column_exclusive: 3
|
||||||
|
stringLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 4
|
||||||
|
last_line: 1
|
||||||
|
last_column: 3
|
||||||
|
last_line_exclusive: 2
|
||||||
|
last_column_exclusive: 0
|
||||||
|
|
||||||
|
checkLocationData '''
|
||||||
|
a"""b
|
||||||
|
"""
|
||||||
|
''',
|
||||||
|
stringWithInterpolationsLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 1
|
||||||
|
last_line: 1
|
||||||
|
last_column: 2
|
||||||
|
last_line_exclusive: 1
|
||||||
|
last_column_exclusive: 3
|
||||||
|
stringLocationData:
|
||||||
|
first_line: 0
|
||||||
|
first_column: 4
|
||||||
|
last_line: 0
|
||||||
|
last_column: 5
|
||||||
|
last_line_exclusive: 1
|
||||||
|
last_column_exclusive: 0
|
||||||
|
|
Loading…
Reference in New Issue