-
Notifications
You must be signed in to change notification settings - Fork 91
Allow single quotes in "raw" string literals #2357
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
8
commits into
main
Choose a base branch
from
always-escape-string-literals
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.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d311c3e
Fully escape all string literals
natebosch 7560445
Temporarily drop build dep
natebosch 410b41e
Edits for clarity
natebosch 858c95e
Merge branch 'main' into always-escape-string-literals
natebosch 3382ecc
Retain raw argument for now
natebosch 9c49ffe
Typo - missing space
natebosch d651e6a
No need to break dep cycle yet
natebosch cf5ab69
Drop conditional
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,90 @@ Expression literalNum(num value) => LiteralExpression._('$value'); | |
|
|
||
| /// Create a literal expression from a string [value]. | ||
| /// | ||
| /// **NOTE**: The string is always formatted `'<value>'`. | ||
| /// Returns an expression for a string formatted `'<value>'`. | ||
| /// | ||
| /// 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. | ||
| /// If [raw] is `true` returns an expression that will evaluate to a String | ||
| /// containing exactly the same content as [value]. The literal may use single | ||
| /// or double quotes, and may not actually be marked raw, depending on the | ||
| /// content. All disallowed characters are automatically escaped. | ||
| Expression literalString(String value, {bool raw = false}) { | ||
| if (raw && value.contains('\'')) { | ||
| throw ArgumentError('Cannot include a single quote in a raw string'); | ||
| } | ||
| if (raw) return LiteralExpression._(_escapeString(value)); | ||
| final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n'); | ||
| return LiteralExpression._("${raw ? 'r' : ''}'$escaped'"); | ||
| return LiteralExpression._("'$escaped'"); | ||
| } | ||
|
|
||
| String _escapeString(String value) { | ||
| final original = value; | ||
| var hasSingleQuote = false; | ||
| var hasDoubleQuote = false; | ||
| var hasDollar = false; | ||
| var hasBackslash = false; | ||
| var canBeRaw = true; | ||
|
|
||
| value = value.replaceAllMapped(_escapeRegExp, (match) { | ||
| final char = match[0]!; | ||
| if (char == "'") { | ||
| hasSingleQuote = true; | ||
| return char; | ||
| } else if (char == '"') { | ||
| hasDoubleQuote = true; | ||
| return char; | ||
| } else if (char == r'$') { | ||
| hasDollar = true; | ||
| return char; | ||
| } else if (char == r'\') { | ||
| hasBackslash = true; | ||
| return r'\\'; | ||
| } | ||
|
|
||
| canBeRaw = false; | ||
| return _escapeMap[char] ?? _hexLiteral(char); | ||
| }); | ||
|
|
||
| if (canBeRaw && (hasDollar || hasBackslash)) { | ||
| if (!hasSingleQuote) return "r'$original'"; | ||
| if (!hasDoubleQuote) return 'r"$original"'; | ||
| } | ||
|
|
||
| if (!hasDollar) { | ||
| if (!hasSingleQuote) return "'$value'"; | ||
| if (!hasDoubleQuote) return '"$value"'; | ||
| } | ||
|
|
||
| value = value.replaceAll(_dollarQuoteRegexp, r'\'); | ||
| return "'$value'"; | ||
| } | ||
|
|
||
| /// 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'; | ||
| } | ||
|
|
||
| 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 | ||
|
Contributor
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. |
||
| }; | ||
|
|
||
| /// A [RegExp] that matches whitespace characters that must be escaped and | ||
| /// single-quote, double-quote, and `$` | ||
| final _escapeRegExp = RegExp('[\$\'"\\x00-\\x07\\x0E-\\x1F$_escapeMapRegexp]'); | ||
|
|
||
| // _escapeMap.keys.map(_hexLiteral).join(); | ||
| const _escapeMapRegexp = r'\x08\x09\x0A\x0B\x0C\x0D\x7F\x5C'; | ||
|
|
||
| /// 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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
_hexLiteralfunction currently uses\xand pads to 2 digits, which is only valid for 8-bit characters (up to\xFF). While the current_escapeRegExponly matches characters within this range, this implementation is fragile if the regex is expanded in the future to include non-ASCII characters. Consider using the more robust\u{...}format which supports all Unicode code points in Dart.