Skip to content

Commit

Permalink
Fix double-escaping inside x-strings and backtiks (fixes #422)
Browse files Browse the repository at this point in the history
  • Loading branch information
adambeynon committed Dec 8, 2013
1 parent 4cd3580 commit 8f646bc
Show file tree
Hide file tree
Showing 10 changed files with 43 additions and 33 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -20,6 +20,9 @@
* Add missing string escapes to `read_escape` in lexer. Now most ruby escape
sequences are properly detected and handled in string parsing.

* Disable escapes inside x-strings. This means no more double escaping all
characters in x-strings and backticks. (`\n` => `\n`).

* Add `time.rb` to stdlib and moved `Time.parse()` and `Time.iso8601()`
methods there.

Expand Down
4 changes: 2 additions & 2 deletions lib/mspec/opal/runner.rb
Expand Up @@ -123,11 +123,11 @@ def finish_with_code(code)

class PhantomFormatter < BrowserFormatter
def green(str)
`console.log('\\033[32m' + str + '\\033[0m')`
`console.log('\033[32m' + str + '\033[0m')`
end

def red(str)
`console.log('\\033[31m' + str + '\\033[0m')`
`console.log('\033[31m' + str + '\033[0m')`
end

def log(str)
Expand Down
2 changes: 1 addition & 1 deletion lib/opal/parser/lexer.rb
Expand Up @@ -417,7 +417,7 @@ def add_string_content(str_buffer, str_parse)
break
elsif scan(/\\/)
if xquote # opal - treat xstrings as dquotes? forces us to double escape
c = self.read_escape
c = "\\" + scan(/./)
elsif qwords and scan(/\n/)
str_buffer << "\n"
next
Expand Down
2 changes: 1 addition & 1 deletion opal/corelib/error.rb
Expand Up @@ -15,7 +15,7 @@ def backtrace
var backtrace = self.stack;
if (typeof(backtrace) === 'string') {
return backtrace.split("\\n").slice(0, 15);
return backtrace.split("\n").slice(0, 15);
}
else if (backtrace) {
return backtrace.slice(0, 15);
Expand Down
2 changes: 1 addition & 1 deletion opal/corelib/kernel.rb
Expand Up @@ -161,7 +161,7 @@ def extend(*mods)
def format(format, *args)
%x{
var idx = 0;
return format.replace(/%(\\d+\\$)?([-+ 0]*)(\\d*|\\*(\\d+\\$)?)(?:\\.(\\d*|\\*(\\d+\\$)?))?([cspdiubBoxXfgeEG])|(%%)/g, function(str, idx_str, flags, width_str, w_idx_str, prec_str, p_idx_str, spec, escaped) {
return format.replace(/%(\d+\$)?([-+ 0]*)(\d*|\*(\d+\$)?)(?:\.(\d*|\*(\d+\$)?))?([cspdiubBoxXfgeEG])|(%%)/g, function(str, idx_str, flags, width_str, w_idx_str, prec_str, p_idx_str, spec, escaped) {
if (escaped) {
return '%';
}
Expand Down
2 changes: 1 addition & 1 deletion opal/corelib/regexp.rb
Expand Up @@ -2,7 +2,7 @@ class Regexp
`def._isRegexp = true`

def self.escape(string)
`string.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\^\\$\\| ]/g, '\\\\$&')`
`string.replace(/[\-\[\]\/\{\}\)\)\*\+\?\.\\\^\$| ]/g, '\\$&')`
end

def self.union(*parts)
Expand Down
42 changes: 21 additions & 21 deletions opal/corelib/string.rb
Expand Up @@ -178,11 +178,11 @@ def chomp(separator = $/)
separator = Opal.coerce_to!(separator, String, :to_str).to_s

%x{
if (separator === "\\n") {
return self.replace(/\\r?\\n?$/, '');
if (separator === "\n") {
return self.replace(/\r?\n?$/, '');
}
else if (separator === "") {
return self.replace(/(\\r?\\n)+$/, '');
return self.replace(/(\r?\n)+$/, '');
}
else if (self.length > separator.length) {
var tail = self.substr(-1 * separator.length);
Expand All @@ -204,7 +204,7 @@ def chop
return "";
}
if (self.charAt(length - 1) === "\\n" && self.charAt(length - 2) === "\\r") {
if (self.charAt(length - 1) === "\n" && self.charAt(length - 2) === "\r") {
return self.substr(0, length - 2);
}
else {
Expand Down Expand Up @@ -386,15 +386,15 @@ def index(what, offset = nil)

def inspect
%x{
var escapable = /[\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g,
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
meta = {
'\\b': '\\\\b',
'\\t': '\\\\t',
'\\n': '\\\\n',
'\\f': '\\\\f',
'\\r': '\\\\r',
'"' : '\\\\"',
'\\\\': '\\\\\\\\'
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
escapable.lastIndex = 0;
Expand All @@ -403,7 +403,7 @@ def inspect
var c = meta[a];
return typeof c === 'string' ? c :
'\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + self + '"';
}
end
Expand Down Expand Up @@ -445,7 +445,7 @@ def ljust(width, padstr = ' ')
end

def lstrip
`self.replace(/^\\s*/, '')`
`self.replace(/^\s*/, '')`
end

def match(pattern, pos = undefined, &block)
Expand Down Expand Up @@ -556,7 +556,7 @@ def rjust(width, padstr = ' ')
end

def rstrip
`self.replace(/\\s*$/, '')`
`self.replace(/\s*$/, '')`
end

def scan(pattern, &block)
Expand Down Expand Up @@ -622,7 +622,7 @@ def split(pattern = $; || ' ', limit = undefined)
}
}
string = (splat == ' ') ? #{self}.replace(/[\\r\\n\\t\\v]\\s+/g, ' ')
string = (splat == ' ') ? #{self}.replace(/[\r\n\t\v]\s+/g, ' ')
: #{self};
while ((cursor = string.indexOf(splat, start)) > -1 && cursor < string.length) {
Expand Down Expand Up @@ -664,7 +664,7 @@ def split(pattern = $; || ' ', limit = undefined)
def squeeze(*sets)
%x{
if (sets.length === 0) {
return self.replace(/(.)\\1+/g, '$1');
return self.replace(/(.)\1+/g, '$1');
}
}

Expand All @@ -679,7 +679,7 @@ def squeeze(*sets)
return self;
}
return self.replace(new RegExp("([" + #{Regexp.escape(`set`.join)} + "])\\\\1+", "g"), "$1");
return self.replace(new RegExp("([" + #{Regexp.escape(`set`.join)} + "])\\1+", "g"), "$1");
}
end

Expand All @@ -698,14 +698,14 @@ def start_with?(*prefixes)
end

def strip
`#{self}.replace(/^\\s*/, '').replace(/\\s*$/, '')`
`#{self}.replace(/^\s*/, '').replace(/\s*$/, '')`
end

def sub(pattern, replace = undefined, &block)
%x{
if (typeof(replace) === 'string') {
// convert Ruby back reference to JavaScript back reference
replace = replace.replace(/\\\\([1-9])/g, '$$$1')
replace = replace.replace(/\\([1-9])/g, '$$$1')
return #{self}.replace(pattern, replace);
}
if (block !== nil) {
Expand Down Expand Up @@ -755,7 +755,7 @@ def sub(pattern, replace = undefined, &block)
}
else {
// convert Ruby back reference to JavaScript back reference
replace = replace.toString().replace(/\\\\([1-9])/g, '$$$1')
replace = replace.toString().replace(/\\([1-9])/g, '$$$1')
return #{self}.replace(pattern, replace);
}
}
Expand Down
10 changes: 5 additions & 5 deletions opal/corelib/time.rb
Expand Up @@ -227,10 +227,10 @@ def zone
result = string.match(/[A-Z]{3,4}/)[0];
}
else {
result = string.match(/\\([^)]+\\)/)[0].match(/[A-Z]/g).join('');
result = string.match(/\([^)]+\)/)[0].match(/[A-Z]/g).join('');
}
if (result == "GMT" && /(GMT\\W*\\d{4})/.test(string)) {
if (result == "GMT" && /(GMT\W*\d{4})/.test(string)) {
return RegExp.$1;
}
else {
Expand All @@ -245,7 +245,7 @@ def gmt_offset

def strftime(format)
%x{
return format.replace(/%([\\-_#^0]*:{0,2})(\\d+)?([EO]*)(.)/g, function(full, flags, width, _, conv) {
return format.replace(/%([\-_#^0]*:{0,2})(\d+)?([EO]*)(.)/g, function(full, flags, width, _, conv) {
var result = "",
width = parseInt(width),
zero = flags.indexOf('0') !== -1,
Expand Down Expand Up @@ -408,11 +408,11 @@ def strftime(format)
break;
case 'n':
result += "\\n";
result += "\n";
break;
case 't':
result += "\\t";
result += "\t";
break;
case '%':
Expand Down
7 changes: 7 additions & 0 deletions spec/cli/compiler_spec.rb
Expand Up @@ -57,6 +57,13 @@
end
end

describe "escapes in x-strings" do
it "compiles the exscapes directly as appearing in x-strings" do
expect_compiled('`"hello\nworld"`').to include('"hello\nworld"')
expect_compiled('%x{"hello\nworld"}').to include('"hello\nworld"')
end
end

def expect_compiled(source)
expect(Opal::Compiler.new.compile source)
end
Expand Down
2 changes: 1 addition & 1 deletion stdlib/strscan.rb
Expand Up @@ -11,7 +11,7 @@ def initialize(string)
end

def bol?
`#@pos === 0 || #@string.charAt(#@pos - 1) === "\\n"`
`#@pos === 0 || #@string.charAt(#@pos - 1) === "\n"`
end

def scan(regex)
Expand Down

0 comments on commit 8f646bc

Please sign in to comment.