+
Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

fix(rome_js_analyzer): escape dollar signs and backticks properly for useTemplate rule #3477

Merged
merged 8 commits into from
Nov 2, 2022

Conversation

nissy-dev
Copy link
Contributor

@nissy-dev nissy-dev commented Oct 23, 2022

Summary

Closes #3451 Escaped ${ properly for useTemplate rule.

Test Plan

I add two test cases and confirmed properly escaped ${.

@netlify
Copy link

netlify bot commented Oct 23, 2022

Deploy Preview for docs-rometools ready!

Name Link
🔨 Latest commit ddcfcb0
🔍 Latest deploy log https://app.netlify.com/sites/docs-rometools/deploys/63613a42f808f80008aae7b5
😎 Deploy Preview https://deploy-preview-3477--docs-rometools.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@@ -336,3 +336,16 @@ fn collect_binary_add_expression(node: &JsBinaryExpression) -> Option<Vec<JsAnyE
};
Some(result)
}

/// Escape dollar sign in raw string.
fn escape_dollar_sign(unescaped_string: &str) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: It would be nice if the function returns a Cow to avoid the extra allocation if the string doesn't contain any ${ (which is the case for the majority of strings).

for (i, _) in unescaped_string.match_indices("${") {
if i == 0 {
chars.insert(0, '\\');
} else if chars[i - 1] != '\\' {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice that you thought of that edge case!

There is one more edge case. It is still necessary to add the \ if the input is "\\${" where the first backslash escapes the second.

This should be converted to \\\${ where the third \ escapes the ${

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MichaReiser Thanks for telling me more edge cases! I have investigated again based on your advice and it looks like I need to escape backticks as well as dollar signs. (ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)

In implementing these escaping operations, I would like to use regex crate. Is it OK?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. An alternative would be to not error on strings that contain backticks because it having a template literal with many escapes may as well be less readable than using + (which we probably can also say is true if the string contains any ${

It should be possible to implement this functionality without using the regex crate as it is only necessary to test for the presence of specific characters. We do something similar in normalize_newlines where we match on a set of characters and then push the result to a new string. The code doesn't allocate for the cases where the input string contains no new lines because an empty string doesn't allocate in Rust.

pub fn normalize_newlines<const N: usize>(text: &str, terminators: [char; N]) -> Cow<str> {
let mut result = String::new();
let mut last_end = 0;
for (start, part) in text.match_indices(terminators) {
result.push_str(&text[last_end..start]);
result.push('\n');
last_end = start + part.len();
// If the current character is \r and the
// next is \n, skip over the entire sequence
if part == "\r" && text[last_end..].starts_with('\n') {
last_end += 1;
}
}
// If the result is empty no line terminators were matched,
// return the entire input text without allocating a new String
if result.is_empty() {
Cow::Borrowed(text)
} else {
result.push_str(&text[last_end..text.len()]);
Cow::Owned(result)
}
}

Copy link
Contributor

@xunilrj xunilrj Oct 25, 2022

Choose a reason for hiding this comment

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

I did a quick test here on a possible generic implementation. I have not spent to much time testing.

We can move this to a util folder and create an extension method for strings.

It is important to implement the already-escaped looking forward. Looking backward doing i-1 it is not UTF8 safe.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=3e3e9e1d460a57170e04b45e3c5d292d

fn escape<'a>(unescaped_string: &'a str, needs_escaping: &[&str], escaping_char: char) -> std::borrow::Cow<'a, str> {
    let mut escaped = String::new();
    let escaping_char_len = escaping_char.len_utf8();
    let mut iter = unescaped_string.char_indices();
    'unescaped: while let Some((idx, chr)) = iter.next() {
       'candidates: for candidate in needs_escaping {
            if chr == escaping_char && unescaped_string[idx + escaping_char_len..].starts_with(candidate) {
                for _ in candidate.chars().skip(1) {
                    let _ = iter.next();
                }
               continue 'candidates;
            }
           
            if unescaped_string[idx..].starts_with(candidate) {
                if escaped.is_empty() {
                    escaped = String::with_capacity(unescaped_string.len() * 2);
                    escaped.push_str(&unescaped_string[0..idx]);
                }
                escaped.push(escaping_char);
                escaped.push_str(candidate);
                for _ in candidate.chars().skip(1) {
                    let _ = iter.next();
                }
                continue 'unescaped;
            }
        }
        
        if !escaped.is_empty() {
            escaped.push(chr);
        }
   }
   
   if escaped.is_empty() {
       std::borrow::Cow::Borrowed(unescaped_string)
   } else {
       std::borrow::Cow::Owned(escaped)
   }
}

@nissy-dev
Copy link
Contributor Author

@xunilrj @MichaReiser Thanks you for your reviews and sorry for my late response! I refered to your comments and reimplemented escape method. I would like you to review again.

@nissy-dev nissy-dev changed the title fix(rome_js_analyzer): escape ${ properly for useTemplate rule fix(rome_js_analyzer): escape dollar signs and backticks properly for useTemplate rule Oct 31, 2022
Copy link
Contributor

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

This is looking good to me and we can get this merged after adding some documentation to escape and potential simplification of the escape implementation

@MichaReiser
Copy link
Contributor

Excellent work. Thank you so much for your contribution! I'll wait for the build to finish and give others some time to chime in. I plan to merge this tomorrow morning (in about 16h ;)).

@MichaReiser MichaReiser merged commit 96a35b4 into rome:main Nov 2, 2022
@nissy-dev nissy-dev deleted the useTemplate-handle-escape-sequence branch January 8, 2023 00:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🐛 useTemplate: Handle Escape Sequence
3 participants
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载