mirror of
https://github.com/oxalica/nil.git
synced 2024-11-22 19:49:20 +03:00
Move string escaping to syntax::semantic
and fix
This commit is contained in:
parent
2552c08e39
commit
fe38ceaae6
@ -3,13 +3,14 @@
|
|||||||
//! - Attribute names <-> Double quoted strings
|
//! - Attribute names <-> Double quoted strings
|
||||||
//! - Double quoted strings <-> Indented strings
|
//! - Double quoted strings <-> Indented strings
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
use super::{AssistKind, AssistsCtx};
|
use super::{AssistKind, AssistsCtx};
|
||||||
use crate::TextEdit;
|
use crate::TextEdit;
|
||||||
use syntax::ast::{self, AstNode};
|
use syntax::ast::{self, AstNode};
|
||||||
use syntax::semantic::{
|
use syntax::semantic::{
|
||||||
is_valid_ident, strip_indent, unescape_string, unescape_string_escape, unescape_string_literal,
|
is_valid_ident, strip_indent, unescape_string, unescape_string_escape, unescape_string_literal,
|
||||||
StrippedStringPart, UnescapedStringPart,
|
EscapeStringFragment, StrippedStringPart, UnescapedStringPart,
|
||||||
};
|
};
|
||||||
use syntax::SyntaxKind;
|
use syntax::SyntaxKind;
|
||||||
|
|
||||||
@ -220,22 +221,32 @@ pub(super) fn rewrite_string_to_indented(ctx: &mut AssistsCtx<'_>) -> Option<()>
|
|||||||
/// ```
|
/// ```
|
||||||
pub(super) fn rewrite_indented_to_string(ctx: &mut AssistsCtx<'_>) -> Option<()> {
|
pub(super) fn rewrite_indented_to_string(ctx: &mut AssistsCtx<'_>) -> Option<()> {
|
||||||
let node = ctx.covering_node::<ast::IndentString>()?;
|
let node = ctx.covering_node::<ast::IndentString>()?;
|
||||||
let mut text = String::from('"');
|
let mut ret = String::from('"');
|
||||||
let _ = strip_indent::<Infallible>(&node, |part| {
|
|
||||||
|
// Concatenate all contiguous fragments and escape them in a single run.
|
||||||
|
// This correctly handles `${` on two individual fragments/escapes.
|
||||||
|
// Eg. `''${` => [Escape("$"), Fragment("{")] => "${" => "\${"
|
||||||
|
// Note that either part alone doesn't require escaping.
|
||||||
|
let mut last_frag = String::new();
|
||||||
|
strip_indent::<Infallible>(&node, |part| {
|
||||||
match part {
|
match part {
|
||||||
StrippedStringPart::Fragment(frag) => {
|
StrippedStringPart::Fragment(frag) => {
|
||||||
escape_dquote_string(&mut text, frag);
|
last_frag += frag;
|
||||||
}
|
}
|
||||||
StrippedStringPart::Escape(esc) => {
|
StrippedStringPart::Escape(esc) => {
|
||||||
escape_dquote_string(&mut text, unescape_string_escape(esc.text()));
|
last_frag += unescape_string_escape(esc.text());
|
||||||
}
|
}
|
||||||
StrippedStringPart::Dynamic(dyna) => {
|
StrippedStringPart::Dynamic(dyna) => {
|
||||||
text += &dyna.syntax().to_string();
|
write!(ret, "{}", EscapeStringFragment(&last_frag)).unwrap();
|
||||||
|
last_frag.clear();
|
||||||
|
ret += &dyna.syntax().to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
})
|
||||||
text.push('"');
|
.unwrap();
|
||||||
|
write!(ret, "{}", EscapeStringFragment(&last_frag)).unwrap();
|
||||||
|
ret.push('"');
|
||||||
|
|
||||||
ctx.add(
|
ctx.add(
|
||||||
"rewrite_indented_to_string",
|
"rewrite_indented_to_string",
|
||||||
@ -243,41 +254,13 @@ pub(super) fn rewrite_indented_to_string(ctx: &mut AssistsCtx<'_>) -> Option<()>
|
|||||||
AssistKind::RefactorRewrite,
|
AssistKind::RefactorRewrite,
|
||||||
vec![TextEdit {
|
vec![TextEdit {
|
||||||
delete: node.syntax().text_range(),
|
delete: node.syntax().text_range(),
|
||||||
insert: text.into(),
|
insert: ret.into(),
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escape `text` and write to `out`,
|
|
||||||
/// `text` may be only a fragment of the original Nix string.
|
|
||||||
fn escape_dquote_string(out: &mut String, text: &str) {
|
|
||||||
let mut xs = text.chars();
|
|
||||||
while let Some(x) = xs.next() {
|
|
||||||
match x {
|
|
||||||
'"' | '\\' => {
|
|
||||||
out.push('\\');
|
|
||||||
out.push(x);
|
|
||||||
}
|
|
||||||
'$' => match xs.next() {
|
|
||||||
Some('{') => out.push_str("\\${"),
|
|
||||||
Some(y) => {
|
|
||||||
out.push('$');
|
|
||||||
out.push(y);
|
|
||||||
}
|
|
||||||
// It's impossible to know from this context whether the next
|
|
||||||
// character is '{' or not, so we assume it is just to be safe
|
|
||||||
None => out.push_str("\\$"),
|
|
||||||
},
|
|
||||||
'\n' => out.push_str("\\n"),
|
|
||||||
'\r' => out.push_str("\\r"),
|
|
||||||
'\t' => out.push_str("\\t"),
|
|
||||||
_ => out.push(x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use expect_test::expect;
|
use expect_test::expect;
|
||||||
@ -364,5 +347,9 @@ mod tests {
|
|||||||
check(r"$0''\n\r\t''", expect![r#""\\n\\r\\t""#]);
|
check(r"$0''\n\r\t''", expect![r#""\\n\\r\\t""#]);
|
||||||
check(r"'''''''$0", expect![r#""''""#]);
|
check(r"'''''''$0", expect![r#""''""#]);
|
||||||
check(r"$0''''${foo}''", expect![r#""\${foo}""#]);
|
check(r"$0''''${foo}''", expect![r#""\${foo}""#]);
|
||||||
|
|
||||||
|
// See comments in `rewrite_indented_to_string`.
|
||||||
|
check(r"$0'' ''${ ''", expect![[r#""\${ ""#]]);
|
||||||
|
check(r"$0'' ''$ ''", expect![[r#""$ ""#]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::ast::{self, AstChildren, AstNode, Attr, Expr, HasStringParts, StringP
|
|||||||
use crate::lexer::KEYWORDS;
|
use crate::lexer::KEYWORDS;
|
||||||
use crate::{SyntaxNode, SyntaxToken};
|
use crate::{SyntaxNode, SyntaxToken};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::str;
|
use std::{fmt, str};
|
||||||
|
|
||||||
/// Check if a name is a valid identifier.
|
/// Check if a name is a valid identifier.
|
||||||
pub fn is_valid_ident(name: &str) -> bool {
|
pub fn is_valid_ident(name: &str) -> bool {
|
||||||
@ -20,15 +20,39 @@ pub fn is_valid_ident(name: &str) -> bool {
|
|||||||
/// Escape a literal Attr. Quote it if it's not a valid identifier.
|
/// Escape a literal Attr. Quote it if it's not a valid identifier.
|
||||||
pub fn escape_literal_attr(name: &str) -> Cow<'_, str> {
|
pub fn escape_literal_attr(name: &str) -> Cow<'_, str> {
|
||||||
if is_valid_ident(name) {
|
if is_valid_ident(name) {
|
||||||
return Cow::Borrowed(name);
|
Cow::Borrowed(name)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(escape_string(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escape the text in a string literal with double-quotes.
|
||||||
|
pub fn escape_string(text: &str) -> String {
|
||||||
|
format!("\"{}\"", EscapeStringFragment(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EscapeStringFragment<'a>(pub &'a str);
|
||||||
|
|
||||||
|
impl fmt::Display for EscapeStringFragment<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for (i, ch) in self.0.char_indices() {
|
||||||
|
match ch {
|
||||||
|
'"' => "\\\"",
|
||||||
|
'\\' => "\\\\",
|
||||||
|
'\n' => "\\n",
|
||||||
|
'\r' => "\\r",
|
||||||
|
'\t' => "\\r",
|
||||||
|
'$' if self.0[i..].starts_with("${") => "\\$",
|
||||||
|
_ => {
|
||||||
|
ch.fmt(f)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fmt(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Cow::Owned(
|
|
||||||
std::iter::empty()
|
|
||||||
.chain(Some('"'))
|
|
||||||
.chain(name.chars().flat_map(|ch| ch.escape_default()))
|
|
||||||
.chain(Some('"'))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unescape a single string escape sequence.
|
/// Unescape a single string escape sequence.
|
||||||
@ -348,6 +372,13 @@ mod tests {
|
|||||||
assert_eq!(escape_literal_attr("in"), r#""in""#);
|
assert_eq!(escape_literal_attr("in"), r#""in""#);
|
||||||
assert_eq!(escape_literal_attr(" "), r#"" ""#);
|
assert_eq!(escape_literal_attr(" "), r#"" ""#);
|
||||||
assert_eq!(escape_literal_attr("\n"), r#""\n""#);
|
assert_eq!(escape_literal_attr("\n"), r#""\n""#);
|
||||||
|
assert_eq!(escape_literal_attr("$ ${"), r#""$ \${""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_string_() {
|
||||||
|
assert_eq!(escape_string(""), r#""""#);
|
||||||
|
assert_eq!(escape_string("n\"$a \n${b"), r#""n\"$a \n\${b""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user