Skip to content

Fully escape all string literals#2357

Draft
natebosch wants to merge 3 commits intomainfrom
always-escape-string-literals
Draft

Fully escape all string literals#2357
natebosch wants to merge 3 commits intomainfrom
always-escape-string-literals

Conversation

@natebosch
Copy link
Copy Markdown
Member

Closes #2351
Closes #2350

I cannot find evidence of use case for writing a string literal that
includes interpolation. Drop the generality and update the doc to
describe a behavior where the content of the resulting string always
matches the content of the argument. Use an implementation from
package:source_helper that attempts to choose an idiomatic delimiter
and falls back to single quotes with every character escaped.

Bump major version since this is technically breaking. Most team usages
should be able to use an expanded constraint.

Closes #2351
Closes #2350

I cannot find evidence of use case for writing a string literal that
includes interpolation. Drop the generality and update the doc to
describe a behavior where the content of the resulting string always
matches the content of the argument. Use an implementation from
`package:source_helper` that attempts to choose an idiomatic delimiter
and falls back to single quotes with every character escaped.

Bump major version since this is technically breaking. Most team usages
should be able to use an expanded constraint.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

Package publishing

Package Version Status Publish tag (post-merge)
package:bazel_worker 1.1.5 already published at pub.dev
package:benchmark_harness 2.4.0 already published at pub.dev
package:boolean_selector 2.1.2 already published at pub.dev
package:browser_launcher 1.2.0-wip WIP (no publish necessary)
package:cli_config 0.2.1-wip WIP (no publish necessary)
package:cli_util 0.5.0-wip WIP (no publish necessary)
package:clock 1.1.3-wip WIP (no publish necessary)
package:code_builder 5.0.0-wip WIP (no publish necessary)
package:coverage 1.15.0 already published at pub.dev
package:csslib 1.0.2 already published at pub.dev
package:extension_discovery 2.1.0 already published at pub.dev
package:file 7.0.2-wip WIP (no publish necessary)
package:file_testing 3.1.0-wip WIP (no publish necessary)
package:glob 2.1.3 already published at pub.dev
package:graphs 2.4.0-wip WIP (no publish necessary)
package:html 0.15.7-wip WIP (no publish necessary)
package:io 1.1.0-wip WIP (no publish necessary)
package:json_rpc_2 4.1.0 already published at pub.dev
package:markdown 7.3.1 already published at pub.dev
package:mime 2.1.0-wip WIP (no publish necessary)
package:oauth2 2.0.5 already published at pub.dev
package:package_config 2.3.0-wip WIP (no publish necessary)
package:pool 1.5.2 already published at pub.dev
package:process 5.0.5 (error) pubspec version (5.0.5) and changelog (5.0.6-wip) don't agree
package:pub_semver 2.2.0 already published at pub.dev
package:pubspec_parse 1.6.0-wip WIP (no publish necessary)
package:source_map_stack_trace 2.1.3-wip WIP (no publish necessary)
package:source_maps 0.10.14-wip WIP (no publish necessary)
package:source_span 1.10.2 already published at pub.dev
package:sse 4.2.0-wip (error) pubspec version (4.2.0-wip) and changelog (4.2.0) don't agree
package:stack_trace 1.12.2-wip (error) pubspec version (1.12.2-wip) and changelog (1.12.2-dev) don't agree
package:stream_channel 2.1.4 already published at pub.dev
package:stream_transform 2.1.2-wip WIP (no publish necessary)
package:string_scanner 1.4.2-wip WIP (no publish necessary)
package:term_glyph 1.2.3-wip WIP (no publish necessary)
package:test_reflective_loader 0.6.0 ready to publish test_reflective_loader-v0.6.0
package:timing 1.0.2 already published at pub.dev
package:unified_analytics 8.0.14 already published at pub.dev
package:watcher 1.2.2-wip WIP (no publish necessary)
package:yaml 3.1.3 already published at pub.dev
package:yaml_edit 2.2.4 already published at pub.dev

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

Coverage ⚠️
File Coverage
pkgs/code_builder/lib/src/specs/expression/literal.dart 💔 Not covered

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

Unused Dependencies ⚠️
Package Status
code_builder
❗ Show Issues
These packages may be unused, or you may be using assets from these packages:
* source_gen

For details on how to fix these, see dependency_validator.

This check can be disabled by tagging the PR with skip-unused-dependencies-check.

Breaking changes ✔️
Package Change Current Version New Version Needed Version Looking good?
code_builder Breaking 4.11.1 5.0.0-wip 5.0.0-wip ✔️

This check can be disabled by tagging the PR with skip-breaking-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

License Headers ✔️
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

Files
no missing headers

All source files should start with a license header.

Unrelated files missing license headers
Files
pkgs/bazel_worker/benchmark/benchmark.dart
pkgs/coverage/lib/src/coverage_options.dart
pkgs/html/example/main.dart
pkgs/html/lib/dom.dart
pkgs/html/lib/dom_parsing.dart
pkgs/html/lib/html_escape.dart
pkgs/html/lib/parser.dart
pkgs/html/lib/src/constants.dart
pkgs/html/lib/src/encoding_parser.dart
pkgs/html/lib/src/html_input_stream.dart
pkgs/html/lib/src/list_proxy.dart
pkgs/html/lib/src/query_selector.dart
pkgs/html/lib/src/token.dart
pkgs/html/lib/src/tokenizer.dart
pkgs/html/lib/src/treebuilder.dart
pkgs/html/lib/src/utils.dart
pkgs/html/test/dom_test.dart
pkgs/html/test/parser_feature_test.dart
pkgs/html/test/parser_test.dart
pkgs/html/test/selectors/level1_baseline_test.dart
pkgs/html/test/selectors/level1_lib.dart
pkgs/html/test/selectors/selectors.dart
pkgs/html/test/support.dart
pkgs/html/test/tokenizer_test.dart
pkgs/html/test/trie_test.dart
pkgs/html/tool/generate_trie.dart
pkgs/pubspec_parse/test/git_uri_test.dart
pkgs/watcher/test/custom_watcher_factory_test.dart

This check can be disabled by tagging the PR with skip-license-check.

@natebosch
Copy link
Copy Markdown
Member Author

Ugh, I forgot that code_builder is one of the hard ones to rev versions.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a breaking change to literalString, removing the raw parameter in favor of an automated escaping mechanism that handles single and double quotes, dollar signs, and control characters. The package version is updated to 5.0.0-wip. Review feedback highlights logic errors in the newly added test cases regarding the expected output for strings with dollar signs. Suggestions were also provided to avoid variable shadowing in the escaping logic and to optimize performance by defining the escape map regular expression as a constant.

});

test('should use raw single quotes for dollar signs if possible', () {
expect(literalString(r'$foo'), equalsDart('r\'\$foo\''));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test appears to be incorrect. The input string to literalString is '$foo'. The generated code should produce this exact string. The implementation correctly generates r'$foo', which evaluates to '$foo'. However, the test expects r'\$foo', which evaluates to '\$foo', a different string containing a literal backslash.

Suggested change
expect(literalString(r'$foo'), equalsDart('r\'\$foo\''));
expect(literalString(r'$foo'), equalsDart(r"'\$foo'"));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The \$ in the test source is escaping at that level, we don't have raw string here, we have non-raw string which contains a raw string. This is already correct, but it's easier to read with equalsDart(r"r'$foo'").


test('should use raw double quotes for dollar signs and single quotes '
'if possible', () {
expect(literalString(r"don't $foo"), equalsDart('r"don\'t \$foo"'));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This test seems to be incorrect. The input string is don't $foo. The generated code should produce this exact string. The implementation correctly generates r"don't $foo", which evaluates to don't $foo. However, the test expects r"don't \$foo", which evaluates to don't \$foo, a different string containing a literal backslash.

      expect(literalString(r"don't $foo"), equalsDart(r'r"don\'t $foo"'));

var canBeRaw = true;

value = value.replaceAllMapped(_escapeRegExp, (match) {
final value = match[0]!;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the Effective Dart style guide, which this repository follows, you should avoid shadowing names. The variable value declared here inside the replaceAllMapped callback shadows the value parameter of the _escapeString function. This can make the code harder to read and understand.

Consider renaming this variable to something like char or matchString and updating its usages within this block.

Suggested change
final value = match[0]!;
final char = match[0]!;
References
  1. This repository's style guide requires following Effective Dart. Effective Dart's style guide recommends avoiding shadowing names to improve readability and prevent bugs. (link)

/// single-quote, double-quote, and `$`
final _escapeRegExp = RegExp('[\$\'"\\x00-\\x07\\x0E-\\x1F$_escapeMapRegexp]');

final _escapeMapRegexp = _escapeMap.keys.map(_hexLiteral).join();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value of _escapeMapRegexp is constant and can be pre-calculated to avoid runtime computation. This improves performance and makes the code more readable.

Please define this as a const string and move its definition before _escapeRegExp.

Suggested change
final _escapeMapRegexp = _escapeMap.keys.map(_hexLiteral).join();
const _escapeMapRegexp = r'\x08\x09\x0A\x0B\x0C\x0D\x7F\x5C';

Avoid a dependency cycle with `package:build_runner` during the major
version bump. We won't be able to regenerate `built_value` classes until
we update build runner for compatibility
@natebosch
Copy link
Copy Markdown
Member Author

@davidmorgan - this change will impact mockito. Currently that package works around the existing behavior. I don't think a true incremental migration is very feasible - we want to retain the literalString name for this behavior.

https://github.com/dart-lang/build/blob/37396be8c572d28010efde9c42f9a9b7c0ded822/builder_pkgs/mockito/lib/src/builder.dart#L2131-L2134

Since mockito is google3 SoT I plan to:

  • land this change
  • manually sync to google3 in advance of the SDK sync. Migrate mockito in the same CL and change dep to ^5.0.0
  • sync to the SDK - will have no effect
  • publish code_builder
  • sync mockito to dart-lang/build. Expand the dep from package:build_runner to '>=4.2.0 <6.0.0'

Do you have any concerns?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant