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:
Konstantin Shcheglov 2021-05-26 02:31:11 +00:00 committed by commit-bot@chromium.org
parent 1731050b29
commit c9e78967d3
6 changed files with 140 additions and 113 deletions

View file

@ -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`.

View file

@ -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.

View file

@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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));
}