Add option to preserve/enforce string quote style

`-q 0` (default) use single or double quotes such as to minimize the number of
 bytes (prefers double quotes when both will do); this is the previous
 behavior.

`-q 1` -- always use single quotes

`-q 2` -- always use double quotes

`-q 3` or just `-q` -- always use the original quotes.

Related codegen option: `quote_style`.

Close #495
Close #460

Some `yargs` guru please tell me why `uglifyjs --help` doesn't display the
help string for `-q` / `--quotes`, and why it doesn't output the expected
argument types anymore, like good old `optimist` did.
This commit is contained in:
Mihai Bazon
2015-01-27 22:26:27 +02:00
parent 099992ecae
commit fbbaa42ee5
5 changed files with 71 additions and 22 deletions

View File

@@ -120,11 +120,12 @@ var AST_Debugger = DEFNODE("Debugger", null, {
$documentation: "Represents a debugger statement",
}, AST_Statement);
var AST_Directive = DEFNODE("Directive", "value scope", {
var AST_Directive = DEFNODE("Directive", "value scope quote", {
$documentation: "Represents a directive, like \"use strict\";",
$propdoc: {
value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
scope: "[AST_Scope/S] The scope that this directive affects"
scope: "[AST_Scope/S] The scope that this directive affects",
quote: "[string] the original quote character"
},
}, AST_Statement);
@@ -766,8 +767,11 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
}
});
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
$documentation: "A key: value object property",
$propdoc: {
quote: "[string] the original quote character"
}
}, AST_ObjectProperty);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
@@ -852,10 +856,11 @@ var AST_Constant = DEFNODE("Constant", null, {
}
});
var AST_String = DEFNODE("String", "value", {
var AST_String = DEFNODE("String", "value quote", {
$documentation: "A string literal",
$propdoc: {
value: "[string] the contents of this string"
value: "[string] the contents of this string",
quote: "[string] the original quote character"
}
}, AST_Constant);

View File

@@ -63,6 +63,7 @@ function OutputStream(options) {
preserve_line : false,
screw_ie8 : false,
preamble : null,
quote_style : 0
}, true);
var indentation = 0;
@@ -84,7 +85,7 @@ function OutputStream(options) {
});
};
function make_string(str) {
function make_string(str, quote) {
var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){
switch (s) {
@@ -102,13 +103,27 @@ function OutputStream(options) {
}
return s;
});
function quote_single() {
return "'" + str.replace(/\x27/g, "\\'") + "'";
}
function quote_double() {
return '"' + str.replace(/\x22/g, '\\"') + '"';
}
if (options.ascii_only) str = to_ascii(str);
if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
else return '"' + str.replace(/\x22/g, '\\"') + '"';
switch (options.quote_style) {
case 1:
return quote_single();
case 2:
return quote_double();
case 3:
return quote == "'" ? quote_single() : quote_double();
default:
return dq > sq ? quote_single() : quote_double();
}
};
function encode_string(str) {
var ret = make_string(str);
function encode_string(str, quote) {
var ret = make_string(str, quote);
if (options.inline_script)
ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
return ret;
@@ -324,7 +339,7 @@ function OutputStream(options) {
force_semicolon : force_semicolon,
to_ascii : to_ascii,
print_name : function(name) { print(make_name(name)) },
print_string : function(str) { print(encode_string(str)) },
print_string : function(str, quote) { print(encode_string(str, quote)) },
next_indent : next_indent,
with_indent : with_indent,
with_block : with_block,
@@ -581,7 +596,7 @@ function OutputStream(options) {
/* -----[ PRINTERS ]----- */
DEFPRINT(AST_Directive, function(self, output){
output.print_string(self.value);
output.print_string(self.value, self.quote);
output.semicolon();
});
DEFPRINT(AST_Debugger, function(self, output){
@@ -1077,6 +1092,7 @@ function OutputStream(options) {
});
DEFPRINT(AST_ObjectKeyVal, function(self, output){
var key = self.key;
var quote = self.quote;
if (output.option("quote_keys")) {
output.print_string(key + "");
} else if ((typeof key == "number"
@@ -1087,7 +1103,7 @@ function OutputStream(options) {
} else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) {
output.print_name(key);
} else {
output.print_string(key);
output.print_string(key, quote);
}
output.colon();
self.value.print(output);
@@ -1125,7 +1141,7 @@ function OutputStream(options) {
output.print(self.getValue());
});
DEFPRINT(AST_String, function(self, output){
output.print_string(self.getValue());
output.print_string(self.getValue(), self.quote);
});
DEFPRINT(AST_Number, function(self, output){
output.print(make_num(self.getValue()));

View File

@@ -367,7 +367,7 @@ function tokenizer($TEXT, filename, html5_comments) {
return num;
};
var read_string = with_eof_error("Unterminated string constant", function(){
var read_string = with_eof_error("Unterminated string constant", function(quote_char){
var quote = next(), ret = "";
for (;;) {
var ch = next(true);
@@ -392,7 +392,9 @@ function tokenizer($TEXT, filename, html5_comments) {
else if (ch == quote) break;
ret += ch;
}
return token("string", ret);
var tok = token("string", ret);
tok.quote = quote_char;
return tok;
});
function skip_line_comment(type) {
@@ -547,7 +549,7 @@ function tokenizer($TEXT, filename, html5_comments) {
if (!ch) return token("eof");
var code = ch.charCodeAt(0);
switch (code) {
case 34: case 39: return read_string();
case 34: case 39: return read_string(ch);
case 46: return handle_dot();
case 47: return handle_slash();
}
@@ -737,8 +739,14 @@ function parse($TEXT, options) {
case "string":
var dir = S.in_directives, stat = simple_statement();
// XXXv2: decide how to fix directives
if (dir && stat.body instanceof AST_String && !is("punc", ","))
return new AST_Directive({ value: stat.body.value });
if (dir && stat.body instanceof AST_String && !is("punc", ",")) {
return new AST_Directive({
start : stat.body.start,
end : stat.body.end,
quote : stat.body.quote,
value : stat.body.value,
});
}
return stat;
case "num":
case "regexp":
@@ -1124,7 +1132,12 @@ function parse($TEXT, options) {
ret = new AST_Number({ start: tok, end: tok, value: tok.value });
break;
case "string":
ret = new AST_String({ start: tok, end: tok, value: tok.value });
ret = new AST_String({
start : tok,
end : tok,
value : tok.value,
quote : tok.quote
});
break;
case "regexp":
ret = new AST_RegExp({ start: tok, end: tok, value: tok.value });
@@ -1237,6 +1250,7 @@ function parse($TEXT, options) {
expect(":");
a.push(new AST_ObjectKeyVal({
start : start,
quote : start.quote,
key : name,
value : expression(false),
end : prev()