Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15a148ff6d | ||
|
|
428e19fed2 | ||
|
|
f65e55dff4 | ||
|
|
b634018618 | ||
|
|
fa3300f314 | ||
|
|
bd0886a2c0 | ||
|
|
248f304f02 | ||
|
|
dc5f70eab5 | ||
|
|
df8c5623af | ||
|
|
a790c09c91 | ||
|
|
8f35a363d9 | ||
|
|
d2190c2bf3 | ||
|
|
ea10642572 | ||
|
|
547561a568 | ||
|
|
c16d538ce7 | ||
|
|
73d082df2e | ||
|
|
50b8d7272c | ||
|
|
7d11b96f48 | ||
|
|
eab99a1c3d | ||
|
|
19e2fb134d | ||
|
|
f4919e3a25 | ||
|
|
dfa395f6ff |
@@ -249,6 +249,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
|||||||
statement would get discarded. The current implementation adds some
|
statement would get discarded. The current implementation adds some
|
||||||
overhead (compression will be slower).
|
overhead (compression will be slower).
|
||||||
|
|
||||||
|
- `drop_console` -- default `false`. Pass `true` to discard calls to
|
||||||
|
`console.*` functions.
|
||||||
|
|
||||||
### The `unsafe` option
|
### The `unsafe` option
|
||||||
|
|
||||||
It enables some transformations that *might* break code logic in certain
|
It enables some transformations that *might* break code logic in certain
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ You need to pass an argument to this option to specify the name that your module
|
|||||||
.describe("lint", "Display some scope warnings")
|
.describe("lint", "Display some scope warnings")
|
||||||
.describe("v", "Verbose")
|
.describe("v", "Verbose")
|
||||||
.describe("V", "Print version number and exit.")
|
.describe("V", "Print version number and exit.")
|
||||||
|
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
|
||||||
|
|
||||||
.alias("p", "prefix")
|
.alias("p", "prefix")
|
||||||
.alias("o", "output")
|
.alias("o", "output")
|
||||||
@@ -96,6 +97,7 @@ You need to pass an argument to this option to specify the name that your module
|
|||||||
.boolean("spidermonkey")
|
.boolean("spidermonkey")
|
||||||
.boolean("lint")
|
.boolean("lint")
|
||||||
.boolean("V")
|
.boolean("V")
|
||||||
|
.boolean("noerr")
|
||||||
|
|
||||||
.wrap(80)
|
.wrap(80)
|
||||||
|
|
||||||
@@ -104,6 +106,12 @@ You need to pass an argument to this option to specify the name that your module
|
|||||||
|
|
||||||
normalize(ARGS);
|
normalize(ARGS);
|
||||||
|
|
||||||
|
if (ARGS.noerr) {
|
||||||
|
UglifyJS.DefaultsError.croak = function(msg, defs) {
|
||||||
|
sys.error("WARN: " + msg);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (ARGS.version || ARGS.V) {
|
if (ARGS.version || ARGS.V) {
|
||||||
var json = require("../package.json");
|
var json = require("../package.json");
|
||||||
sys.puts(json.name + ' ' + json.version);
|
sys.puts(json.name + ' ' + json.version);
|
||||||
|
|||||||
@@ -498,12 +498,6 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", {
|
|||||||
}
|
}
|
||||||
}, AST_Block);
|
}, AST_Block);
|
||||||
|
|
||||||
// XXX: this is wrong according to ECMA-262 (12.4). the catch block
|
|
||||||
// should introduce another scope, as the argname should be visible
|
|
||||||
// only inside the catch block. However, doing it this way because of
|
|
||||||
// IE which simply introduces the name in the surrounding scope. If
|
|
||||||
// we ever want to fix this then AST_Catch should inherit from
|
|
||||||
// AST_Scope.
|
|
||||||
var AST_Catch = DEFNODE("Catch", "argname", {
|
var AST_Catch = DEFNODE("Catch", "argname", {
|
||||||
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
|
$documentation: "A `catch` node; only makes sense as part of a `try` statement",
|
||||||
$propdoc: {
|
$propdoc: {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ function Compressor(options, false_by_default) {
|
|||||||
pure_funcs : null,
|
pure_funcs : null,
|
||||||
negate_iife : !false_by_default,
|
negate_iife : !false_by_default,
|
||||||
screw_ie8 : false,
|
screw_ie8 : false,
|
||||||
|
drop_console : false,
|
||||||
|
|
||||||
warnings : true,
|
warnings : true,
|
||||||
global_defs : {}
|
global_defs : {}
|
||||||
@@ -85,13 +86,14 @@ merge(Compressor.prototype, {
|
|||||||
},
|
},
|
||||||
before: function(node, descend, in_list) {
|
before: function(node, descend, in_list) {
|
||||||
if (node._squeezed) return node;
|
if (node._squeezed) return node;
|
||||||
|
var was_scope = false;
|
||||||
if (node instanceof AST_Scope) {
|
if (node instanceof AST_Scope) {
|
||||||
//node.drop_unused(this);
|
|
||||||
node = node.hoist_declarations(this);
|
node = node.hoist_declarations(this);
|
||||||
|
was_scope = true;
|
||||||
}
|
}
|
||||||
descend(node, this);
|
descend(node, this);
|
||||||
node = node.optimize(this);
|
node = node.optimize(this);
|
||||||
if (node instanceof AST_Scope) {
|
if (was_scope && node instanceof AST_Scope) {
|
||||||
node.drop_unused(this);
|
node.drop_unused(this);
|
||||||
descend(node, this);
|
descend(node, this);
|
||||||
}
|
}
|
||||||
@@ -1668,10 +1670,10 @@ merge(Compressor.prototype, {
|
|||||||
return arg.value;
|
return arg.value;
|
||||||
}).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
|
}).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
|
||||||
var ast = parse(code);
|
var ast = parse(code);
|
||||||
ast.figure_out_scope();
|
ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
|
||||||
var comp = new Compressor(compressor.options);
|
var comp = new Compressor(compressor.options);
|
||||||
ast = ast.transform(comp);
|
ast = ast.transform(comp);
|
||||||
ast.figure_out_scope();
|
ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
|
||||||
ast.mangle_names();
|
ast.mangle_names();
|
||||||
var fun;
|
var fun;
|
||||||
try {
|
try {
|
||||||
@@ -1772,6 +1774,14 @@ merge(Compressor.prototype, {
|
|||||||
return make_node(AST_Undefined, self).transform(compressor);
|
return make_node(AST_Undefined, self).transform(compressor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (compressor.option("drop_console")) {
|
||||||
|
if (self.expression instanceof AST_PropAccess &&
|
||||||
|
self.expression.expression instanceof AST_SymbolRef &&
|
||||||
|
self.expression.expression.name == "console" &&
|
||||||
|
self.expression.expression.undeclared()) {
|
||||||
|
return make_node(AST_Undefined, self).transform(compressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
return self.evaluate(compressor)[0];
|
return self.evaluate(compressor)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1810,9 +1820,15 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
if (compressor.option("cascade")) {
|
if (compressor.option("cascade")) {
|
||||||
if (self.car instanceof AST_Assign
|
if (self.car instanceof AST_Assign
|
||||||
&& !self.car.left.has_side_effects(compressor)
|
&& !self.car.left.has_side_effects(compressor)) {
|
||||||
&& self.car.left.equivalent_to(self.cdr)) {
|
if (self.car.left.equivalent_to(self.cdr)) {
|
||||||
return self.car;
|
return self.car;
|
||||||
|
}
|
||||||
|
if (self.cdr instanceof AST_Call
|
||||||
|
&& self.cdr.expression.equivalent_to(self.car.left)) {
|
||||||
|
self.cdr.expression = self.car;
|
||||||
|
return self.cdr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!self.car.has_side_effects(compressor)
|
if (!self.car.has_side_effects(compressor)
|
||||||
&& !self.cdr.has_side_effects(compressor)
|
&& !self.cdr.has_side_effects(compressor)
|
||||||
@@ -1865,6 +1881,14 @@ merge(Compressor.prototype, {
|
|||||||
return self.evaluate(compressor)[0];
|
return self.evaluate(compressor)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function has_side_effects_or_prop_access(node, compressor) {
|
||||||
|
var save_pure_getters = compressor.option("pure_getters");
|
||||||
|
compressor.options.pure_getters = false;
|
||||||
|
var ret = node.has_side_effects(compressor);
|
||||||
|
compressor.options.pure_getters = save_pure_getters;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
|
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
|
||||||
if (compressor.option("sequences")) {
|
if (compressor.option("sequences")) {
|
||||||
if (this.left instanceof AST_Seq) {
|
if (this.left instanceof AST_Seq) {
|
||||||
@@ -1876,8 +1900,8 @@ merge(Compressor.prototype, {
|
|||||||
return seq;
|
return seq;
|
||||||
}
|
}
|
||||||
if (this.right instanceof AST_Seq
|
if (this.right instanceof AST_Seq
|
||||||
&& !(this.operator == "||" || this.operator == "&&")
|
&& this instanceof AST_Assign
|
||||||
&& !this.left.has_side_effects(compressor)) {
|
&& !has_side_effects_or_prop_access(this.left, compressor)) {
|
||||||
var seq = this.right;
|
var seq = this.right;
|
||||||
var x = seq.to_array();
|
var x = seq.to_array();
|
||||||
this.right = x.pop();
|
this.right = x.pop();
|
||||||
@@ -2022,16 +2046,6 @@ merge(Compressor.prototype, {
|
|||||||
&& self.right.getValue() === "" && self.left instanceof AST_Binary
|
&& self.right.getValue() === "" && self.left instanceof AST_Binary
|
||||||
&& self.left.operator == "+" && self.left.is_string(compressor)) {
|
&& self.left.operator == "+" && self.left.is_string(compressor)) {
|
||||||
return self.left;
|
return self.left;
|
||||||
} else if (self.operator == "+" && self.right instanceof AST_String
|
|
||||||
&& self.right.getValue() === "" && self.left instanceof AST_Binary
|
|
||||||
&& self.left.operator == "+" && self.left.right instanceof AST_Number) {
|
|
||||||
return make_node(AST_Binary, self, {
|
|
||||||
left: self.left.left,
|
|
||||||
operator: "+",
|
|
||||||
right: make_node(AST_String, self.right, {
|
|
||||||
value: String(self.left.right.value)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (compressor.option("evaluate")) {
|
if (compressor.option("evaluate")) {
|
||||||
if (self.operator == "+") {
|
if (self.operator == "+") {
|
||||||
|
|||||||
49
lib/scope.js
49
lib/scope.js
@@ -71,27 +71,34 @@ SymbolDef.prototype = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||||
// This does what ast_add_scope did in UglifyJS v1.
|
options = defaults(options, {
|
||||||
//
|
screw_ie8: false
|
||||||
// Part of it could be done at parse time, but it would complicate
|
});
|
||||||
// the parser (and it's already kinda complex). It's also worth
|
|
||||||
// having it separated because we might need to call it multiple
|
|
||||||
// times on the same tree.
|
|
||||||
|
|
||||||
// pass 1: setup scope chaining and handle definitions
|
// pass 1: setup scope chaining and handle definitions
|
||||||
var self = this;
|
var self = this;
|
||||||
var scope = self.parent_scope = null;
|
var scope = self.parent_scope = null;
|
||||||
|
var defun = null;
|
||||||
var nesting = 0;
|
var nesting = 0;
|
||||||
var tw = new TreeWalker(function(node, descend){
|
var tw = new TreeWalker(function(node, descend){
|
||||||
|
if (options.screw_ie8 && node instanceof AST_Catch) {
|
||||||
|
var save_scope = scope;
|
||||||
|
scope = new AST_Scope(node);
|
||||||
|
scope.init_scope_vars(nesting);
|
||||||
|
scope.parent_scope = save_scope;
|
||||||
|
descend();
|
||||||
|
scope = save_scope;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (node instanceof AST_Scope) {
|
if (node instanceof AST_Scope) {
|
||||||
node.init_scope_vars(nesting);
|
node.init_scope_vars(nesting);
|
||||||
var save_scope = node.parent_scope = scope;
|
var save_scope = node.parent_scope = scope;
|
||||||
++nesting;
|
var save_defun = defun;
|
||||||
scope = node;
|
defun = scope = node;
|
||||||
descend();
|
++nesting; descend(); --nesting;
|
||||||
scope = save_scope;
|
scope = save_scope;
|
||||||
--nesting;
|
defun = save_defun;
|
||||||
return true; // don't descend again in TreeWalker
|
return true; // don't descend again in TreeWalker
|
||||||
}
|
}
|
||||||
if (node instanceof AST_Directive) {
|
if (node instanceof AST_Directive) {
|
||||||
@@ -108,7 +115,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
|||||||
node.scope = scope;
|
node.scope = scope;
|
||||||
}
|
}
|
||||||
if (node instanceof AST_SymbolLambda) {
|
if (node instanceof AST_SymbolLambda) {
|
||||||
scope.def_function(node);
|
defun.def_function(node);
|
||||||
}
|
}
|
||||||
else if (node instanceof AST_SymbolDefun) {
|
else if (node instanceof AST_SymbolDefun) {
|
||||||
// Careful here, the scope where this should be defined is
|
// Careful here, the scope where this should be defined is
|
||||||
@@ -116,22 +123,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
|||||||
// scope when we encounter the AST_Defun node (which is
|
// scope when we encounter the AST_Defun node (which is
|
||||||
// instanceof AST_Scope) but we get to the symbol a bit
|
// instanceof AST_Scope) but we get to the symbol a bit
|
||||||
// later.
|
// later.
|
||||||
(node.scope = scope.parent_scope).def_function(node);
|
(node.scope = defun.parent_scope).def_function(node);
|
||||||
}
|
}
|
||||||
else if (node instanceof AST_SymbolVar
|
else if (node instanceof AST_SymbolVar
|
||||||
|| node instanceof AST_SymbolConst) {
|
|| node instanceof AST_SymbolConst) {
|
||||||
var def = scope.def_variable(node);
|
var def = defun.def_variable(node);
|
||||||
def.constant = node instanceof AST_SymbolConst;
|
def.constant = node instanceof AST_SymbolConst;
|
||||||
def.init = tw.parent().value;
|
def.init = tw.parent().value;
|
||||||
}
|
}
|
||||||
else if (node instanceof AST_SymbolCatch) {
|
else if (node instanceof AST_SymbolCatch) {
|
||||||
// XXX: this is wrong according to ECMA-262 (12.4). the
|
(options.screw_ie8 ? scope : defun)
|
||||||
// `catch` argument name should be visible only inside the
|
.def_variable(node);
|
||||||
// catch block. For a quick fix AST_Catch should inherit
|
|
||||||
// from AST_Scope. Keeping it this way because of IE,
|
|
||||||
// which doesn't obey the standard. (it introduces the
|
|
||||||
// identifier in the enclosing scope)
|
|
||||||
scope.def_variable(node);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.walk(tw);
|
self.walk(tw);
|
||||||
@@ -244,6 +246,11 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){
|
|||||||
out: while (true) {
|
out: while (true) {
|
||||||
var m = base54(++this.cname);
|
var m = base54(++this.cname);
|
||||||
if (!is_identifier(m)) continue; // skip over "do"
|
if (!is_identifier(m)) continue; // skip over "do"
|
||||||
|
|
||||||
|
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not
|
||||||
|
// shadow a name excepted from mangling.
|
||||||
|
if (options.except.indexOf(m) >= 0) continue;
|
||||||
|
|
||||||
// we must ensure that the mangled name does not shadow a name
|
// we must ensure that the mangled name does not shadow a name
|
||||||
// from some parent scope that is referenced in this or in
|
// from some parent scope that is referenced in this or in
|
||||||
// inner scopes.
|
// inner scopes.
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ function SourceMap(options) {
|
|||||||
file : null,
|
file : null,
|
||||||
root : null,
|
root : null,
|
||||||
orig : null,
|
orig : null,
|
||||||
|
|
||||||
|
orig_line_diff : 0,
|
||||||
|
dest_line_diff : 0,
|
||||||
});
|
});
|
||||||
var generator = new MOZ_SourceMap.SourceMapGenerator({
|
var generator = new MOZ_SourceMap.SourceMapGenerator({
|
||||||
file : options.file,
|
file : options.file,
|
||||||
@@ -67,8 +70,8 @@ function SourceMap(options) {
|
|||||||
name = info.name;
|
name = info.name;
|
||||||
}
|
}
|
||||||
generator.addMapping({
|
generator.addMapping({
|
||||||
generated : { line: gen_line, column: gen_col },
|
generated : { line: gen_line + options.dest_line_diff, column: gen_col },
|
||||||
original : { line: orig_line, column: orig_col },
|
original : { line: orig_line + options.orig_line_diff, column: orig_col },
|
||||||
source : source,
|
source : source,
|
||||||
name : name
|
name : name
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,16 +82,23 @@ function repeat_string(str, i) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function DefaultsError(msg, defs) {
|
function DefaultsError(msg, defs) {
|
||||||
|
Error.call(this, msg);
|
||||||
this.msg = msg;
|
this.msg = msg;
|
||||||
this.defs = defs;
|
this.defs = defs;
|
||||||
};
|
};
|
||||||
|
DefaultsError.prototype = Object.create(Error.prototype);
|
||||||
|
DefaultsError.prototype.constructor = DefaultsError;
|
||||||
|
|
||||||
|
DefaultsError.croak = function(msg, defs) {
|
||||||
|
throw new DefaultsError(msg, defs);
|
||||||
|
};
|
||||||
|
|
||||||
function defaults(args, defs, croak) {
|
function defaults(args, defs, croak) {
|
||||||
if (args === true)
|
if (args === true)
|
||||||
args = {};
|
args = {};
|
||||||
var ret = args || {};
|
var ret = args || {};
|
||||||
if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i))
|
if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i))
|
||||||
throw new DefaultsError("`" + i + "` is not a supported option", defs);
|
DefaultsError.croak("`" + i + "` is not a supported option", defs);
|
||||||
for (var i in defs) if (defs.hasOwnProperty(i)) {
|
for (var i in defs) if (defs.hasOwnProperty(i)) {
|
||||||
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i];
|
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||||
"homepage": "http://lisperator.net/uglifyjs",
|
"homepage": "http://lisperator.net/uglifyjs",
|
||||||
"main": "tools/node.js",
|
"main": "tools/node.js",
|
||||||
"version": "2.4.2",
|
"version": "2.4.8",
|
||||||
"engines": { "node" : ">=0.4.0" },
|
"engines": { "node" : ">=0.4.0" },
|
||||||
"maintainers": [{
|
"maintainers": [{
|
||||||
"name": "Mihai Bazon",
|
"name": "Mihai Bazon",
|
||||||
|
|||||||
@@ -119,3 +119,47 @@ unused_keep_setter_arg: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unused_var_in_catch: {
|
||||||
|
options = { unused: true };
|
||||||
|
input: {
|
||||||
|
function foo() {
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch(ex) {
|
||||||
|
var x = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
function foo() {
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch(ex) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
used_var_in_catch: {
|
||||||
|
options = { unused: true };
|
||||||
|
input: {
|
||||||
|
function foo() {
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch(ex) {
|
||||||
|
var x = 10;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
function foo() {
|
||||||
|
try {
|
||||||
|
foo();
|
||||||
|
} catch(ex) {
|
||||||
|
var x = 10;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,15 +54,13 @@ strings_concat: {
|
|||||||
input: {
|
input: {
|
||||||
f(
|
f(
|
||||||
String(x + 'str'),
|
String(x + 'str'),
|
||||||
String('str' + x),
|
String('str' + x)
|
||||||
String(x + 5)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
f(
|
f(
|
||||||
x + 'str',
|
x + 'str',
|
||||||
'str' + x,
|
'str' + x
|
||||||
x + '5'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,10 +101,12 @@ lift_sequences_1: {
|
|||||||
lift_sequences_2: {
|
lift_sequences_2: {
|
||||||
options = { sequences: true, evaluate: true };
|
options = { sequences: true, evaluate: true };
|
||||||
input: {
|
input: {
|
||||||
q = 1 + (foo(), bar(), 5) + 7 * (5 / (3 - (a(), (QW=ER), c(), 2))) - (x(), y(), 5);
|
foo.x = (foo = {}, 10);
|
||||||
|
bar = (bar = {}, 10);
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
foo(), bar(), a(), QW = ER, c(), x(), y(), q = 36
|
foo.x = (foo = {}, 10),
|
||||||
|
bar = {}, bar = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user