-
Notifications
You must be signed in to change notification settings - Fork 90
Add fullEscape argument to literalString #2352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
natebosch
wants to merge
6
commits into
main
Choose a base branch
from
literal-string-doc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+160
−6
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
940695e
Escape carriage returns in literalString
natebosch be84ae2
Merge branch 'main' into literal-string-doc
natebosch acd7035
Phrasing tweak
natebosch 44bd45d
Add test showing \ should not be re-escaped
natebosch 78aa68a
Rewrite as raw string
natebosch 9f86539
Add a fullEscape argument
natebosch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,19 +41,104 @@ Expression literalNum(num value) => LiteralExpression._('$value'); | |
|
|
||
| /// Create a literal expression from a string [value]. | ||
| /// | ||
| /// **NOTE**: The string is always formatted `'<value>'`. | ||
| /// The _content_ of [value] is used as the _source_ of the generated Dart | ||
| /// string wrapped in single quotes. For example, `literalString('\$foo')` will | ||
| /// generate `'$foo'`, which will be an interpolation in the generated source. | ||
| /// | ||
| /// If [raw] is `true`, creates a raw String formatted `r'<value>'` and the | ||
| /// value may not contain a single quote. | ||
| /// Escapes single quotes and newlines in the value. | ||
| Expression literalString(String value, {bool raw = false}) { | ||
| /// If the content of [value] is intended to match the content of the generated | ||
| /// literal use the [fullEscape] argument to create a String expression with | ||
| /// whatever escaping is necessary to result in the same content. This may | ||
| /// result in a raw string. | ||
| /// | ||
| /// To force a raw String use the [raw] argument to create a string formatted | ||
| /// `r'<value>'`. For example `literalString('\$foo', raw: true)` will generate | ||
| /// `r'$foo'` which includes a `$` character. Most callers will prefer | ||
| /// [fullEscape]. | ||
| /// | ||
| /// When [raw] is `true`, the value may not contain any single quotes. | ||
| /// When [raw] and [fullEscape] are `false`, single quotes are escaped. | ||
| /// | ||
| /// Newlines and carriage returns are always escaped outside of triple quoted | ||
| /// strings to avoid invalid syntax. | ||
| Expression literalString( | ||
| String value, { | ||
| bool raw = false, | ||
| bool fullEscape = false, | ||
| }) { | ||
| if (fullEscape) return LiteralExpression._(_escapeString(value)); | ||
| if (raw && value.contains('\'')) { | ||
| throw ArgumentError('Cannot include a single quote in a raw string'); | ||
| } | ||
| final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n'); | ||
| final escaped = value | ||
| .replaceAll('\'', '\\\'') | ||
| .replaceAll('\n', '\\n') | ||
| .replaceAll('\r', '\\r'); | ||
| return LiteralExpression._("${raw ? 'r' : ''}'$escaped'"); | ||
| } | ||
|
|
||
| String _escapeString(String value) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks familiar 😁 |
||
| var hasSingleQuote = false; | ||
| var hasDoubleQuote = false; | ||
| var hasDollar = false; | ||
| var canBeRaw = true; | ||
|
|
||
| value = value.replaceAllMapped(_escapeRegExp, (match) { | ||
| final value = match[0]!; | ||
| if (value == "'") { | ||
| hasSingleQuote = true; | ||
| return value; | ||
| } else if (value == '"') { | ||
| hasDoubleQuote = true; | ||
| return value; | ||
| } else if (value == r'$') { | ||
| hasDollar = true; | ||
| return value; | ||
| } | ||
|
|
||
| canBeRaw = false; | ||
| return _escapeMap[value] ?? _hexLiteral(value); | ||
| }); | ||
|
|
||
| if (!hasDollar) { | ||
| if (!hasSingleQuote) return "'$value'"; | ||
| if (!hasDoubleQuote) return '"$value"'; | ||
| } else if (canBeRaw) { | ||
| if (!hasSingleQuote) return "r'$value'"; | ||
| if (!hasDoubleQuote) return 'r"$value"'; | ||
| } | ||
| value = value.replaceAll(_dollarQuoteRegexp, r'\'); | ||
| return "'$value'"; | ||
| } | ||
|
|
||
| final _dollarQuoteRegexp = RegExp(r"(?=[$'])"); | ||
|
|
||
| /// A map from whitespace characters & `\` to their escape sequences. | ||
| const _escapeMap = { | ||
| '\b': r'\b', // 08 - backspace | ||
| '\t': r'\t', // 09 - tab | ||
| '\n': r'\n', // 0A - new line | ||
| '\v': r'\v', // 0B - vertical tab | ||
| '\f': r'\f', // 0C - form feed | ||
| '\r': r'\r', // 0D - carriage return | ||
| '\x7F': r'\x7F', // delete | ||
| r'\': r'\\', // backslash | ||
| }; | ||
|
|
||
| /// Given single-character string, return the hex-escaped equivalent. | ||
| String _hexLiteral(String input) { | ||
| final value = input.runes.single | ||
| .toRadixString(16) | ||
| .toUpperCase() | ||
| .padLeft(2, '0'); | ||
| return '\\x$value'; | ||
| } | ||
|
|
||
| /// A [RegExp] that matches whitespace characters that must be escaped and | ||
| /// single-quote, double-quote, and `$` | ||
| final _escapeRegExp = RegExp('[\$\'"\\x00-\\x07\\x0E-\\x1F$_escapeMapRegexp]'); | ||
|
|
||
| final _escapeMapRegexp = _escapeMap.keys.map(_hexLiteral).join(); | ||
|
|
||
| /// Create a literal `...` operator for use when creating a Map literal. | ||
| /// | ||
| /// *NOTE* This is used as a sentinel when constructing a `literalMap` or a | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.