mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:27:39 +00:00
Add StringInterpolation.firstString and lastString.
There are several places in linter where we cast unconditionally. Potentially we could also have `firstExpression` because there is always at least one. Change-Id: I9c3e754de6c8872941c1f1a47e36a6cbd3714b4a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201362 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
1731050b29
commit
c9e78967d3
|
@ -1,3 +1,8 @@
|
|||
## 1.8.0-dev
|
||||
* Added `StringInterpolation.firstString` and `lastString`, to express
|
||||
explicitly that there are always (possibly empty) strings as the first
|
||||
and the last elements of an interpolation.
|
||||
|
||||
## 1.7.0
|
||||
* Require `meta: ^1.4.0`.
|
||||
|
||||
|
|
|
@ -5152,7 +5152,18 @@ abstract class Statement implements AstNode {
|
|||
/// Clients may not extend, implement or mix-in this class.
|
||||
abstract class StringInterpolation implements SingleStringLiteral {
|
||||
/// Return the elements that will be composed to produce the resulting string.
|
||||
/// The list includes [firstString] and [lastString].
|
||||
NodeList<InterpolationElement> get elements;
|
||||
|
||||
/// Return the first element in this interpolation, which is always a string.
|
||||
/// The string might be empty if there is no text before the first
|
||||
/// interpolation expression (such as in `'$foo bar'`).
|
||||
InterpolationString get firstString;
|
||||
|
||||
/// Return the last element in this interpolation, which is always a string.
|
||||
/// The string might be empty if there is no text after the last
|
||||
/// interpolation expression (such as in `'foo $bar'`).
|
||||
InterpolationString get lastString;
|
||||
}
|
||||
|
||||
/// A string literal expression.
|
||||
|
|
|
@ -9167,6 +9167,20 @@ class StringInterpolationImpl extends SingleStringLiteralImpl
|
|||
|
||||
/// Initialize a newly created string interpolation expression.
|
||||
StringInterpolationImpl(List<InterpolationElement> elements) {
|
||||
// TODO(scheglov) Replace asserts with appropriately typed parameters.
|
||||
assert(elements.length > 2, 'Expected at last three elements.');
|
||||
assert(
|
||||
elements.first is InterpolationStringImpl,
|
||||
'The first element must be a string.',
|
||||
);
|
||||
assert(
|
||||
elements[1] is InterpolationExpressionImpl,
|
||||
'The second element must be an expression.',
|
||||
);
|
||||
assert(
|
||||
elements.last is InterpolationStringImpl,
|
||||
'The last element must be a string.',
|
||||
);
|
||||
_elements._initialize(this, elements);
|
||||
}
|
||||
|
||||
|
@ -9196,6 +9210,10 @@ class StringInterpolationImpl extends SingleStringLiteralImpl
|
|||
@override
|
||||
Token get endToken => _elements.endToken!;
|
||||
|
||||
@override
|
||||
InterpolationStringImpl get firstString =>
|
||||
elements.first as InterpolationStringImpl;
|
||||
|
||||
@override
|
||||
bool get isMultiline => _firstHelper.isMultiline;
|
||||
|
||||
|
@ -9205,6 +9223,10 @@ class StringInterpolationImpl extends SingleStringLiteralImpl
|
|||
@override
|
||||
bool get isSingleQuoted => _firstHelper.isSingleQuoted;
|
||||
|
||||
@override
|
||||
InterpolationStringImpl get lastString =>
|
||||
elements.last as InterpolationStringImpl;
|
||||
|
||||
StringLexemeHelper get _firstHelper {
|
||||
var lastString = _elements.first as InterpolationString;
|
||||
String lexeme = lastString.contents.lexeme;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: analyzer
|
||||
version: 1.7.0
|
||||
version: 1.8.0-dev
|
||||
description: This package provides a library that performs static analysis of Dart code.
|
||||
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/analyzer
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast_factory.dart';
|
||||
|
@ -430,171 +431,155 @@ class IndexExpressionTest {
|
|||
|
||||
@reflectiveTest
|
||||
class InterpolationStringTest extends ParserTestCase {
|
||||
InterpolationString interpolationString(
|
||||
String lexeme, String value, bool isFirst, bool isLast) {
|
||||
var node = AstTestFactory.interpolationString(lexeme, value);
|
||||
var nodes = <InterpolationElement>[
|
||||
if (!isFirst) AstTestFactory.interpolationString("'first", "first"),
|
||||
node,
|
||||
if (!isLast) AstTestFactory.interpolationString("last'", "last")
|
||||
];
|
||||
var parent = AstTestFactory.string(nodes);
|
||||
assert(node.parent == parent);
|
||||
return node;
|
||||
}
|
||||
/// This field is updated in [_parseStringInterpolation].
|
||||
/// It is used in [_assertContentsOffsetEnd].
|
||||
var _baseOffset = 0;
|
||||
|
||||
void test_contentsOffset_doubleQuote_first() {
|
||||
var node = interpolationString('"foo', "foo", true, true);
|
||||
expect(node.contentsOffset, '"'.length);
|
||||
expect(node.contentsEnd, '"'.length + "foo".length);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_firstLast() {
|
||||
var node = interpolationString('"foo"', "foo", true, true);
|
||||
expect(node.contentsOffset, '"'.length);
|
||||
expect(node.contentsEnd, '"'.length + "foo".length);
|
||||
var interpolation = _parseStringInterpolation('"foo\$x last"');
|
||||
var node = interpolation.firstString;
|
||||
_assertContentsOffsetEnd(node, 1, 4);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_last() {
|
||||
var node = interpolationString('foo"', "foo", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo".length);
|
||||
var interpolation = _parseStringInterpolation('"first \$x foo"');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 13);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_last_empty() {
|
||||
var node = interpolationString('"', "", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, 0);
|
||||
var interpolation = _parseStringInterpolation('"first \$x"');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 9);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_last_unterminated() {
|
||||
var node = interpolationString('foo', "foo", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo".length);
|
||||
var interpolation = _parseStringInterpolation('"first \$x foo');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 13);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_multiline_first() {
|
||||
var node = interpolationString('"""\nfoo\n', "foo\n", true, true);
|
||||
expect(node.contentsOffset, '"""\n'.length);
|
||||
expect(node.contentsEnd, '"""\n'.length + "foo\n".length);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_multiline_firstLast() {
|
||||
var node = interpolationString('"""\nfoo\n"""', "foo\n", true, true);
|
||||
expect(node.contentsOffset, '"""\n'.length);
|
||||
expect(node.contentsEnd, '"""\n'.length + "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation('"""foo\n\$x last"""');
|
||||
var node = interpolation.firstString;
|
||||
_assertContentsOffsetEnd(node, 3, 7);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_multiline_last() {
|
||||
var node = interpolationString('foo\n"""', "foo\n", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation('"""first\$x foo\n"""');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 15);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_multiline_last_empty() {
|
||||
var node = interpolationString('"""', "", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, 0);
|
||||
var interpolation = _parseStringInterpolation('"""first\$x"""');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 10);
|
||||
}
|
||||
|
||||
void test_contentsOffset_doubleQuote_multiline_last_unterminated() {
|
||||
var node = interpolationString('foo\n', "foo\n", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation('"""first\$x foo\n');
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 15);
|
||||
}
|
||||
|
||||
void test_contentsOffset_escapeCharacters() {
|
||||
// Contents offset cannot use 'value' string, because of escape sequences.
|
||||
var node = interpolationString(r'"foo\nbar"', "foo\nbar", true, true);
|
||||
expect(node.contentsOffset, '"'.length);
|
||||
expect(node.contentsEnd, '"'.length + "foo\\nbar".length);
|
||||
var interpolation = _parseStringInterpolation(r'"foo\nbar$x last"');
|
||||
var node = interpolation.firstString;
|
||||
_assertContentsOffsetEnd(node, 1, 9);
|
||||
}
|
||||
|
||||
void test_contentsOffset_middle() {
|
||||
var node = interpolationString("foo", "foo", false, false);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo".length);
|
||||
var interpolation =
|
||||
_parseStringInterpolation(r'"first $x foo\nbar $y last"');
|
||||
var node = interpolation.elements[2] as InterpolationString;
|
||||
_assertContentsOffsetEnd(node, 9, 19);
|
||||
}
|
||||
|
||||
void test_contentsOffset_middle_quoteBegin() {
|
||||
// This occurs in, for instance, `"$a'foo$b"`
|
||||
var node = interpolationString("'foo", "'foo", false, false);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "'foo".length);
|
||||
var interpolation = _parseStringInterpolation('"first \$x \'foo\$y last"');
|
||||
var node = interpolation.elements[2] as InterpolationString;
|
||||
_assertContentsOffsetEnd(node, 9, 14);
|
||||
}
|
||||
|
||||
void test_contentsOffset_middle_quoteBeginEnd() {
|
||||
// This occurs in, for instance, `"$a'foo'$b"`
|
||||
var node = interpolationString("'foo'", "'foo'", false, false);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "'foo'".length);
|
||||
var interpolation =
|
||||
_parseStringInterpolation('"first \$x \'foo\'\$y last"');
|
||||
var node = interpolation.elements[2] as InterpolationString;
|
||||
_assertContentsOffsetEnd(node, 9, 15);
|
||||
}
|
||||
|
||||
void test_contentsOffset_middle_quoteEnd() {
|
||||
// This occurs in, for instance, `"${a}foo'$b"`
|
||||
var node = interpolationString("foo'", "foo'", false, false);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo'".length);
|
||||
var interpolation = _parseStringInterpolation('"first \$x foo\'\$y last"');
|
||||
var node = interpolation.elements[2] as InterpolationString;
|
||||
_assertContentsOffsetEnd(node, 9, 14);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_first() {
|
||||
var node = interpolationString("'foo", "foo", true, true);
|
||||
expect(node.contentsOffset, "'".length);
|
||||
expect(node.contentsEnd, "'".length + "foo".length);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_firstLast() {
|
||||
var node = interpolationString("'foo'", "foo", true, true);
|
||||
expect(node.contentsOffset, "'".length);
|
||||
expect(node.contentsEnd, "'".length + "foo".length);
|
||||
var interpolation = _parseStringInterpolation("'foo\$x last'");
|
||||
var node = interpolation.firstString;
|
||||
_assertContentsOffsetEnd(node, 1, 4);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_last() {
|
||||
var node = interpolationString("foo'", "foo", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo".length);
|
||||
var interpolation = _parseStringInterpolation("'first \$x foo'");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 13);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_last_empty() {
|
||||
var node = interpolationString("'", "", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, 0);
|
||||
var interpolation = _parseStringInterpolation("'first \$x'");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 9);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_last_unterminated() {
|
||||
var node = interpolationString("foo", "foo", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo".length);
|
||||
var interpolation = _parseStringInterpolation("'first \$x");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 9, 9);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_multiline_first() {
|
||||
var node = interpolationString("'''\nfoo\n", "foo\n", true, true);
|
||||
expect(node.contentsOffset, "'''\n".length);
|
||||
expect(node.contentsEnd, "'''\n".length + "foo\n".length);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_multiline_firstLast() {
|
||||
var node = interpolationString("'''\nfoo\n'''", "foo\n", true, true);
|
||||
expect(node.contentsOffset, "'''\n".length);
|
||||
expect(node.contentsEnd, "'''\n".length + "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation("'''foo\n\$x last'''");
|
||||
var node = interpolation.firstString;
|
||||
_assertContentsOffsetEnd(node, 3, 7);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_multiline_last() {
|
||||
var node = interpolationString("foo\n'''", "foo\n", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation("'''first\$x foo\n'''");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 15);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_multiline_last_empty() {
|
||||
var node = interpolationString("'''", "", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, 0);
|
||||
var interpolation = _parseStringInterpolation("'''first\$x'''");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 10);
|
||||
}
|
||||
|
||||
void test_contentsOffset_singleQuote_multiline_last_unterminated() {
|
||||
var node = interpolationString("foo\n", "foo\n", false, true);
|
||||
expect(node.contentsOffset, 0);
|
||||
expect(node.contentsEnd, "foo\n".length);
|
||||
var interpolation = _parseStringInterpolation("'''first\$x'''");
|
||||
var node = interpolation.lastString;
|
||||
_assertContentsOffsetEnd(node, 10, 10);
|
||||
}
|
||||
|
||||
void _assertContentsOffsetEnd(InterpolationString node, int offset, int end) {
|
||||
expect(node.contentsOffset, _baseOffset + offset);
|
||||
expect(node.contentsEnd, _baseOffset + end);
|
||||
}
|
||||
|
||||
StringInterpolation _parseStringInterpolation(String code) {
|
||||
var unitCode = 'var x = ';
|
||||
_baseOffset = unitCode.length;
|
||||
unitCode += code;
|
||||
var unit = parseString(
|
||||
content: unitCode,
|
||||
throwIfDiagnostics: false,
|
||||
).unit;
|
||||
var declaration = unit.declarations[0] as TopLevelVariableDeclaration;
|
||||
return declaration.variables.variables[0].initializer
|
||||
as StringInterpolation;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1587,13 +1572,14 @@ class SpreadElementTest extends ParserTestCase {
|
|||
@reflectiveTest
|
||||
class StringInterpolationTest extends ParserTestCase {
|
||||
void test_contentsOffsetEnd() {
|
||||
AstTestFactory.interpolationExpression(AstTestFactory.identifier3('bb'));
|
||||
var bb = AstTestFactory.interpolationExpression(
|
||||
AstTestFactory.identifier3('bb'));
|
||||
// 'a${bb}ccc'
|
||||
{
|
||||
var ae = AstTestFactory.interpolationString("'a", "a");
|
||||
var cToken = StringToken(TokenType.STRING, "ccc'", 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 1);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1602,7 +1588,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
var ae = AstTestFactory.interpolationString("'''a", "a");
|
||||
var cToken = StringToken(TokenType.STRING, "ccc'''", 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 3);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1611,7 +1597,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
var ae = AstTestFactory.interpolationString('"""a', "a");
|
||||
var cToken = StringToken(TokenType.STRING, 'ccc"""', 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 3);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1620,7 +1606,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
var ae = AstTestFactory.interpolationString("r'a", "a");
|
||||
var cToken = StringToken(TokenType.STRING, "ccc'", 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 2);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1629,7 +1615,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
var ae = AstTestFactory.interpolationString("r'''a", "a");
|
||||
var cToken = StringToken(TokenType.STRING, "ccc'''", 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 4);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1638,7 +1624,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
var ae = AstTestFactory.interpolationString('r"""a', "a");
|
||||
var cToken = StringToken(TokenType.STRING, 'ccc"""', 10);
|
||||
var cElement = astFactory.interpolationString(cToken, 'ccc');
|
||||
StringInterpolation node = AstTestFactory.string([ae, ae, cElement]);
|
||||
StringInterpolation node = AstTestFactory.string([ae, bb, cElement]);
|
||||
expect(node.contentsOffset, 4);
|
||||
expect(node.contentsEnd, 10 + 4 - 1);
|
||||
}
|
||||
|
@ -1678,7 +1664,7 @@ class StringInterpolationTest extends ParserTestCase {
|
|||
}
|
||||
|
||||
void test_isRaw() {
|
||||
StringInterpolation node = AstTestFactory.string();
|
||||
var node = parseStringLiteral('"first \$x last"') as StringInterpolation;
|
||||
expect(node.isRaw, isFalse);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
|
@ -3356,8 +3357,10 @@ class NodeReplacerTest {
|
|||
}
|
||||
|
||||
void test_stringInterpolation() {
|
||||
StringInterpolation node =
|
||||
AstTestFactory.string([AstTestFactory.interpolationExpression2("a")]);
|
||||
var unit = parseString(content: 'var v = "first \$x last";').unit;
|
||||
var declaration = unit.declarations[0] as TopLevelVariableDeclaration;
|
||||
var variable = declaration.variables.variables[0];
|
||||
var node = variable.initializer as StringInterpolation;
|
||||
_assertReplace(
|
||||
node, ListGetter_NodeReplacerTest_test_stringInterpolation(0));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue