Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
438 lines (370 sloc) 13.8 KB
###############################################################################
#
# Copyright (c) 2011 Ruslan Spivak
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
import re
from slimit import ast
from slimit.lexer import Lexer
_HAS_ID_MATCH = re.compile('^%s$' % Lexer.identifier).match
def _is_identifier(value):
return _HAS_ID_MATCH(value) and value not in Lexer.keywords_dict
class ECMAMinifier(object):
def __init__(self):
self.in_block = 0
self.ifelse_stack = []
def visit(self, node):
method = 'visit_%s' % node.__class__.__name__
return getattr(self, method, self.generic_visit)(node)
def generic_visit(self, node):
return 'GEN: %r' % node
def visit_Program(self, node):
return ''.join(self.visit(child) for child in node)
def visit_Block(self, node):
children = [self.visit(child) for child in node]
if len(children) == 1:
return children[0]
else:
return '{%s}' % ''.join(children)
def visit_VarStatement(self, node):
s = 'var %s;' % ','.join(self.visit(child) for child in node)
return s
def visit_VarDecl(self, node):
output = []
output.append(self.visit(node.identifier))
if node.initializer is not None:
output.append('=%s' % self.visit(node.initializer))
return ''.join(output)
def visit_Identifier(self, node):
return node.value
def visit_Assign(self, node):
template = '%s%s%s'
if getattr(node, '_parens', False):
template = '(%s)' % template
return template % (
self.visit(node.left), node.op, self.visit(node.right))
def visit_GetPropAssign(self, node):
template = 'get %s(){%s}'
if getattr(node, '_parens', False):
template = '(%s)' % template
return template % (
self.visit(node.prop_name),
''.join(self.visit(element) for element in node.elements)
)
def visit_SetPropAssign(self, node):
template = 'set %s(%s){%s}'
if getattr(node, '_parens', False):
template = '(%s)' % template
if len(node.parameters) > 1:
raise SyntaxError(
'Setter functions must have one argument: %s' % node)
return template % (
self.visit(node.prop_name),
''.join(self.visit(param) for param in node.parameters),
''.join(self.visit(element) for element in node.elements)
)
def visit_Number(self, node):
return node.value
def visit_Comma(self, node):
template = '%s,%s'
if getattr(node, '_parens', False):
template = '(%s)' % template
return template % (self.visit(node.left), self.visit(node.right))
def visit_EmptyStatement(self, node):
return node.value
def visit_If(self, node):
has_alternative = node.alternative is not None
def _is_singleline_block(n):
return isinstance(n, ast.Block) and (len(n.children()) == 1)
s = 'if('
if node.predicate is not None:
s += self.visit(node.predicate)
s += ')'
# if we are an 'if..else' statement and 'if' part contains only
# one statement
if has_alternative and _is_singleline_block(node.consequent):
self.ifelse_stack.append({'if_in_ifelse': False})
consequent = self.visit(node.consequent)
record = self.ifelse_stack.pop()
if record['if_in_ifelse']:
s += '{%s}' % consequent
else:
s += consequent
elif has_alternative:
# we are an 'if..else' statement and 'if' part contains
# myltiple statements
s += self.visit(node.consequent)
else:
# 'if' without alternative - mark it so that an enclosing
# 'if..else' can act on it and add braces around 'if' part
if self.ifelse_stack:
self.ifelse_stack[-1]['if_in_ifelse'] = True
s += self.visit(node.consequent)
if has_alternative:
alternative = self.visit(node.alternative)
if alternative.startswith(('(', '{')):
s += 'else%s' % alternative
else:
s += 'else %s' % alternative
return s
def visit_Boolean(self, node):
return node.value
def visit_For(self, node):
s = 'for('
if node.init is not None:
s += self.visit(node.init)
if node.init is None:
s += ';'
elif isinstance(node.init, (ast.Assign, ast.Comma, ast.Conditional,
ast.FunctionCall, ast.UnaryOp,
ast.Identifier)):
s += ';'
else:
s += ''
if node.cond is not None:
s += self.visit(node.cond)
s += ';'
if node.count is not None:
s += self.visit(node.count)
s += ')' + self.visit(node.statement)
return s
def visit_ForIn(self, node):
if isinstance(node.item, ast.VarDecl):
template = 'for(var %s in %s)'
else:
template = 'for(%s in %s)'
s = template % (self.visit(node.item), self.visit(node.iterable))
s += self.visit(node.statement)
return s
def visit_BinOp(self, node):
if node.op in ('instanceof', 'in'):
template = '%s %s %s'
elif (node.op == '+' and
isinstance(node.right, ast.UnaryOp) and
node.right.op == '++' and not node.right.postfix
):
# make a space between + and ++
# https://github.com/rspivak/slimit/issues/26
template = '%s%s %s'
else:
template = '%s%s%s'
if getattr(node, '_parens', False):
template = '(%s)' % template
return template % (
self.visit(node.left), node.op, self.visit(node.right))
def visit_UnaryOp(self, node):
s = self.visit(node.value)
if node.postfix:
s += node.op
elif node.op in ('delete', 'void', 'typeof'):
s = '%s %s' % (node.op, s)
else:
s = '%s%s' % (node.op, s)
if getattr(node, '_parens', False):
s = '(%s)' % s
return s
def visit_ExprStatement(self, node):
return '%s;' % self.visit(node.expr)
def visit_DoWhile(self, node):
statement = self.visit(node.statement)
if statement.startswith(('{', '(')):
s = 'do%s' % statement
else:
s = 'do %s' % statement
s += 'while(%s);' % self.visit(node.predicate)
return s
def visit_While(self, node):
s = 'while(%s)' % self.visit(node.predicate)
s += self.visit(node.statement)
return s
def visit_Null(self, node):
return 'null'
def visit_String(self, node):
return node.value
def visit_Continue(self, node):
if node.identifier is not None:
s = 'continue %s;' % self.visit_Identifier(node.identifier)
else:
s = 'continue;'
return s
def visit_Break(self, node):
if node.identifier is not None:
s = 'break %s;' % self.visit_Identifier(node.identifier)
else:
s = 'break;'
return s
def visit_Return(self, node):
if node.expr is None:
return 'return;'
expr_text = self.visit(node.expr)
if expr_text.startswith(('(', '{')):
return 'return%s;' % expr_text
else:
return 'return %s;' % expr_text
def visit_With(self, node):
s = 'with(%s)' % self.visit(node.expr)
s += self.visit(node.statement)
return s
def visit_Label(self, node):
s = '%s:%s' % (
self.visit(node.identifier), self.visit(node.statement))
return s
def visit_Switch(self, node):
s = 'switch(%s){' % self.visit(node.expr)
for case in node.cases:
s += self.visit_Case(case)
if node.default is not None:
s += self.visit_Default(node.default)
s += '}'
return s
def visit_Case(self, node):
s = 'case %s:' % self.visit(node.expr)
elements = ''.join(self.visit(element) for element in node.elements)
if elements:
s += elements
return s
def visit_Default(self, node):
s = 'default:'
s += ''.join(self.visit(element) for element in node.elements)
if node.elements is not None:
s += ''
return s
def visit_Throw(self, node):
s = 'throw %s;' % self.visit(node.expr)
return s
def visit_Debugger(self, node):
return '%s;' % node.value
def visit_Try(self, node):
result = self.visit(node.statements)
if result.startswith('{'):
s = 'try%s' % result
else:
s = 'try{%s}' % result
if node.catch is not None:
s += self.visit(node.catch)
if node.fin is not None:
s += self.visit(node.fin)
return s
def visit_Catch(self, node):
ident = self.visit(node.identifier)
result = self.visit(node.elements)
if result.startswith('{'):
s = 'catch(%s)%s' % (ident, result)
else:
s = 'catch(%s){%s}' % (ident, result)
return s
def visit_Finally(self, node):
result = self.visit(node.elements)
if result.startswith('{'):
s = 'finally%s' % result
else:
s = 'finally{%s}' % result
return s
def visit_FuncDecl(self, node):
elements = ''.join(self.visit(element) for element in node.elements)
s = 'function %s(%s){%s' % (
self.visit(node.identifier),
','.join(self.visit(param) for param in node.parameters),
elements,
)
s += '}'
return s
def visit_FuncExpr(self, node):
elements = ''.join(self.visit(element) for element in node.elements)
ident = node.identifier
ident = '' if ident is None else ' %s' % self.visit(ident)
header = 'function%s(%s)'
if getattr(node, '_parens', False):
header = '(' + header
s = (header + '{%s') % (
ident,
','.join(self.visit(param) for param in node.parameters),
elements,
)
s += '}'
if getattr(node, '_parens', False):
s += ')'
return s
def visit_Conditional(self, node):
if getattr(node, '_parens', False):
template = '(%s?%s:%s)'
else:
template = '%s?%s:%s'
s = template % (
self.visit(node.predicate),
self.visit(node.consequent), self.visit(node.alternative))
return s
def visit_Regex(self, node):
if getattr(node, '_parens', False):
return '(%s)' % node.value
else:
return node.value
def visit_NewExpr(self, node):
s = 'new %s(%s)' % (
self.visit(node.identifier),
','.join(self.visit(arg) for arg in node.args)
)
return s
def visit_DotAccessor(self, node):
if getattr(node, '_parens', False):
template = '(%s.%s)'
else:
template = '%s.%s'
s = template % (self.visit(node.node), self.visit(node.identifier))
return s
def visit_BracketAccessor(self, node):
if isinstance(node.expr, ast.String):
value = node.expr.value
# remove single or double quotes around the value, but not both
if value.startswith("'"):
value = value.strip("'")
elif value.startswith('"'):
value = value.strip('"')
if _is_identifier(value):
s = '%s.%s' % (self.visit(node.node), value)
return s
s = '%s[%s]' % (self.visit(node.node), self.visit(node.expr))
return s
def visit_FunctionCall(self, node):
template = '%s(%s)'
if getattr(node, '_parens', False):
template = '(%s)' % template
s = template % (self.visit(node.identifier),
','.join(self.visit(arg) for arg in node.args))
return s
def visit_Object(self, node):
s = '{%s}' % ','.join(self.visit(prop) for prop in node.properties)
return s
def visit_Array(self, node):
s = '['
length = len(node.items) - 1
for index, item in enumerate(node.items):
if isinstance(item, ast.Elision):
s += ','
elif index != length:
s += self.visit(item) + ','
else:
s += self.visit(item)
s += ']'
return s
def visit_This(self, node):
return 'this'
Jump to Line
Something went wrong with that request. Please try again.