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

@@ -352,6 +352,13 @@ can pass additional arguments that control the code output:
it will be prepended to the output literally. The source map will it will be prepended to the output literally. The source map will
adjust for this text. Can be used to insert a comment containing adjust for this text. Can be used to insert a comment containing
licensing information, for example. licensing information, for example.
- `quote_style` (default `0`) -- preferred quote style for strings (affects
quoted property names and directives as well):
- `0` -- prefers double quotes, switches to single quotes when there are
more double quotes in the string itself.
- `1` -- always use single quotes
- `2` -- always use double quotes
- `3` -- always use the original quotes
### Keeping copyright notices or other comments ### Keeping copyright notices or other comments

View File

@@ -66,6 +66,7 @@ You need to pass an argument to this option to specify the name that your module
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.") .describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
.describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.") .describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.")
.describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.") .describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.")
.describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)")
.alias("p", "prefix") .alias("p", "prefix")
.alias("o", "output") .alias("o", "output")
@@ -77,6 +78,7 @@ You need to pass an argument to this option to specify the name that your module
.alias("r", "reserved") .alias("r", "reserved")
.alias("V", "version") .alias("V", "version")
.alias("e", "enclose") .alias("e", "enclose")
.alias("q", "quotes")
.string("source-map") .string("source-map")
.string("source-map-root") .string("source-map-root")
@@ -151,9 +153,14 @@ if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/); if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
} }
if (ARGS.quotes === true) {
ARGS.quotes = 3;
}
var OUTPUT_OPTIONS = { var OUTPUT_OPTIONS = {
beautify: BEAUTIFY ? true : false, beautify : BEAUTIFY ? true : false,
preamble: ARGS.preamble || null, preamble : ARGS.preamble || null,
quote_style : ARGS.quotes != null ? ARGS.quotes : 0
}; };
if (ARGS.screw_ie8) { if (ARGS.screw_ie8) {

View File

@@ -120,11 +120,12 @@ var AST_Debugger = DEFNODE("Debugger", null, {
$documentation: "Represents a debugger statement", $documentation: "Represents a debugger statement",
}, AST_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\";", $documentation: "Represents a directive, like \"use strict\";",
$propdoc: { $propdoc: {
value: "[string] The value of this directive as a plain string (it's not an AST_String!)", 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); }, 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", $documentation: "A key: value object property",
$propdoc: {
quote: "[string] the original quote character"
}
}, AST_ObjectProperty); }, AST_ObjectProperty);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { 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", $documentation: "A string literal",
$propdoc: { $propdoc: {
value: "[string] the contents of this string" value: "[string] the contents of this string",
quote: "[string] the original quote character"
} }
}, AST_Constant); }, AST_Constant);

View File

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

View File

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