aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Baines <mail@cbaines.net>2015-12-22 17:03:30 +0000
committerChristopher Baines <mail@cbaines.net>2015-12-22 17:03:30 +0000
commit3f41d4ade1401421756550dc0cdf0046677c7f08 (patch)
tree99b02d064ba6b357b51e53cc2df6a10fd44999ae
parent05199198d690228f2a030c0f007e23819a997f0b (diff)
downloadpython-jsmin-upstream.tar
python-jsmin-upstream.tar.gz
Import python-jsmin_2.2.0.orig.tar.gzupstream-2.2.0upstream
-rw-r--r--.gitignore8
-rw-r--r--.hgignore10
-rw-r--r--.hgtags5
-rw-r--r--.travis.yml10
-rw-r--r--CHANGELOG.txt60
-rw-r--r--PKG-INFO92
-rw-r--r--README.rst48
-rw-r--r--jsmin.egg-info/PKG-INFO92
-rw-r--r--jsmin.egg-info/SOURCES.txt12
-rw-r--r--jsmin.egg-info/dependency_links.txt1
-rw-r--r--jsmin.egg-info/top_level.txt1
-rw-r--r--jsmin/__init__.py226
-rw-r--r--jsmin/test.py324
-rw-r--r--setup.py11
14 files changed, 573 insertions, 327 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea7c850
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+bin/
+build/
+dist/
+include/
+*.egg-info/
+__pycache__
+*.pyc
+lib/
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..e67fe71
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,10 @@
+syntax: glob
+*.pyc
+build/
+dist/
+jsmin.egg-info
+local/
+lib/
+bin/
+include/
+js_tests/
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 0000000..4fb2bc4
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,5 @@
+f02a6b63d03891a833fe5c0882e1e6fbed56a18f release-2.0.7
+13c9fde88ad8f99f87ee98afeef8d75fd8c37c08 release-2.0.8
+6f478019608c43d95fd7cbedffe58527213bbafc release-2.0.9
+39e5030a91de951b051449bde4178c32308c9b6b release-2.0.10
+4e64a7c1e036f18a3cbba88a1cf5fef7bc8ecda8 release-2.0.11
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..02e0344
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: python
+python:
+ - "3.3"
+ - "3.2"
+ - "2.7"
+ - "2.6"
+# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
+install:
+# command to run tests, e.g. python setup.py test
+script: python setup.py test \ No newline at end of file
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
new file mode 100644
index 0000000..b9aea65
--- /dev/null
+++ b/CHANGELOG.txt
@@ -0,0 +1,60 @@
+Changelog
+=========
+
+v2.2.0 (2015-12-19) Tikitu de Jager
+-----------------------------------
+
+- Merge #13: Preserve "loud comments" starting with `/*!`
+
+ These are commonly used for copyright notices, and are preserved by various
+ other minifiers (e.g. YUI Compressor).
+
+v2.1.6 (2015-10-14) Tikitu de Jager
+-----------------------------------
+
+- Fix #12: Newline following a regex literal should not be elided.
+
+v2.1.5 (2015-10-11) Tikitu de Jager
+-----------------------------------
+
+- Fix #9: Premature end of statement caused by multi-line comment not
+ adding newline.
+
+- Fix #10: Removing multiline comment separating tokens must leave a space.
+
+- Refactor comment handling for maintainability.
+
+v2.1.4 (2015-08-23) Tikitu de Jager
+-----------------------------------
+
+- Fix #6: regex literal matching comment was not correctly matched.
+
+- Refactor regex literal handling for robustness.
+
+v2.1.3 (2015-08-09) Tikitu de Jager
+-----------------------------------
+
+- Reset issue numbering: issues live in github from now on.
+
+- Fix #1: regex literal was not recognised when occurring directly after `{`.
+
+v2.1.2 (2015-07-12) Tikitu de Jager
+-----------------------------------
+
+- Issue numbers here and below refer to the bitbucket repository.
+
+- Fix #17: bug when JS starts with comment then literal regex.
+
+v2.1.1 (2015-02-14) Tikitu de Jager
+-----------------------------------
+
+- Fix #16: bug returning a literal regex containing escaped forward-slashes.
+
+v2.1.0 (2014-12-24) Tikitu de Jager
+-----------------------------------
+
+- First changelog entries; see README.rst for prior contributors.
+
+- Expose quote_chars parameter to provide just enough unofficial Harmony
+ support to be useful.
+
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index bbba187..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,92 +0,0 @@
-Metadata-Version: 1.1
-Name: jsmin
-Version: 2.0.9
-Summary: JavaScript minifier.
-PLEASE UPDATE TO VERSION >= 2.0.6. Older versions have a serious bug related to comments.
-Home-page: https://bitbucket.org/dcs/jsmin/
-Author: Tikitu de Jager
-Author-email: tikitu+jsmin@logophile.org
-License: MIT License
-Description: =====
- jsmin
- =====
-
- JavaScript minifier.
-
- Usage
- =====
-
- .. code:: python
-
- from jsmin import jsmin
- with open('myfile.js') as js_file:
- minified = jsmin(js_file.read())
-
- You can run it as a commandline tool also::
-
- python -m jsmin myfile.js
-
- Where to get it
- ===============
-
- * install the package `from pypi <https://pypi.python.org/pypi/jsmin/>`_
- * get the latest release `from the stable branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/stable>`_
- * get the development version `from the default branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/default>`_
-
- Contributing
- ============
-
- `Issues <https://bitbucket.org/dcs/jsmin/issues>`_ and `Pull requests <https://bitbucket.org/dcs/jsmin/pull-requests>`_
- will be gratefully received on Bitbucket. Pull requests on github are great too, but the issue tracker lives on
- bitbucket.
-
- If possible, please make separate pull requests for tests and for code: tests will be committed on the stable branch
- (which tracks the latest released version) while code will go to default by, erm, default.
-
- Unless you request otherwise, your Bitbucket identity will be added to the contributor's list below; if you prefer a
- different name feel free to add it in your pull request instead. (If you prefer not to be mentioned you'll have to let
- the maintainer know somehow.)
-
- Build/test status
- =================
-
- Both default and stable branches are tested with Travis: https://travis-ci.org/tikitu/jsmin
-
- Stable (latest released version plus any new tests) is tested against CPython 2.6, 2.7, 3.2, and 3.3.
- Currently:
-
- .. image:: https://travis-ci.org/tikitu/jsmin.png?branch=ghstable
-
- If stable is failing that means there's a new test that fails on *the latest released version on pypi*, with no fix yet
- released.
-
- Default (development version, might be ahead of latest released version) is tested against CPython 2.6, 2.7, 3.2, and
- 3.3. Currently:
-
- .. image:: https://travis-ci.org/tikitu/jsmin.png?branch=master
-
- If default is failing don't use it, but as long as stable is passing the pypi release should be ok.
-
- Contributors (chronological commit order)
- =========================================
-
- * `Dave St.Germain <https://bitbucket.org/dcs>`_ (original author)
- * `Hans weltar <https://bitbucket.org/hansweltar>`_
- * `Tikitu de Jager <mailto:tikitu+jsmin@logophile.org>`_ (current maintainer)
- * https://bitbucket.org/rennat
-
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Software Development :: Pre-processors
-Classifier: Topic :: Text Processing :: Filters
diff --git a/README.rst b/README.rst
index ade024a..e7a5755 100644
--- a/README.rst
+++ b/README.rst
@@ -17,46 +17,63 @@ You can run it as a commandline tool also::
python -m jsmin myfile.js
+NB: ``jsmin`` makes no attempt to be compatible with
+`ECMAScript 6 / ES.next / Harmony <http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts>`_.
+The current maintainer does not intend to add ES6-compatibility. If you would
+like to take over maintenance and update ``jsmin`` for ES6, please contact
+`Tikitu de Jager <mailto:tikitu+jsmin@logophile.org>`_. Pull requests are also
+welcome, of course, but my time to review them is somewhat limited these days.
+
+If you're using ``jsmin`` on ES6 code, though, you might find the ``quote_chars``
+parameter useful:
+
+.. code:: python
+
+ from jsmin import jsmin
+ with open('myfile.js') as js_file:
+ minified = jsmin(js_file.read(), quote_chars="'\"`")
+
+
Where to get it
===============
* install the package `from pypi <https://pypi.python.org/pypi/jsmin/>`_
-* get the latest release `from the stable branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/stable>`_
-* get the development version `from the default branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/default>`_
+* get the latest release `from latest-release on github <https://github.com/tikitu/jsmin/tree/latest-release/jsmin>`_
+* get the development version `from master on github <https://github.com/tikitu/jsmin/>`_
Contributing
============
-`Issues <https://bitbucket.org/dcs/jsmin/issues>`_ and `Pull requests <https://bitbucket.org/dcs/jsmin/pull-requests>`_
-will be gratefully received on Bitbucket. Pull requests on github are great too, but the issue tracker lives on
-bitbucket.
+`Issues <https://github.com/tikitu/jsmin/issues>`_ and `Pull requests <https://github.com/tikitu/jsmin/pulls>`_
+will be gratefully received on Github. The project used to be hosted
+`on bitbucket <https://bitbucket.org/dcs/jsmin/>`_ and old issues can still be
+found there.
-If possible, please make separate pull requests for tests and for code: tests will be committed on the stable branch
-(which tracks the latest released version) while code will go to default by, erm, default.
+If possible, please make separate pull requests for tests and for code: tests will be added to the `latest-release` branch while code will go to `master`.
-Unless you request otherwise, your Bitbucket identity will be added to the contributor's list below; if you prefer a
+Unless you request otherwise, your Github identity will be added to the contributor's list below; if you prefer a
different name feel free to add it in your pull request instead. (If you prefer not to be mentioned you'll have to let
the maintainer know somehow.)
Build/test status
=================
-Both default and stable branches are tested with Travis: https://travis-ci.org/tikitu/jsmin
+Both branches are tested with Travis: https://travis-ci.org/tikitu/jsmin
-Stable (latest released version plus any new tests) is tested against CPython 2.6, 2.7, 3.2, and 3.3.
+The `latest-release` branch (the version on PyPI plus any new tests) is tested against CPython 2.6, 2.7, 3.2, and 3.3.
Currently:
-.. image:: https://travis-ci.org/tikitu/jsmin.png?branch=ghstable
+.. image:: https://travis-ci.org/tikitu/jsmin.png?branch=latest-release
-If stable is failing that means there's a new test that fails on *the latest released version on pypi*, with no fix yet
+If that branch is failing that means there's a new test that fails on *the latest released version on pypi*, with no fix yet
released.
-Default (development version, might be ahead of latest released version) is tested against CPython 2.6, 2.7, 3.2, and
+The `master` branch (development version, might be ahead of latest released version) is tested against CPython 2.6, 2.7, 3.2, and
3.3. Currently:
.. image:: https://travis-ci.org/tikitu/jsmin.png?branch=master
-If default is failing don't use it, but as long as stable is passing the pypi release should be ok.
+If `master` is failing don't use it, but as long as `latest-release` is passing the pypi release should be ok.
Contributors (chronological commit order)
=========================================
@@ -65,3 +82,6 @@ Contributors (chronological commit order)
* `Hans weltar <https://bitbucket.org/hansweltar>`_
* `Tikitu de Jager <mailto:tikitu+jsmin@logophile.org>`_ (current maintainer)
* https://bitbucket.org/rennat
+* `Nick Alexander <https://bitbucket.org/ncalexan>`_
+* `Gennady Kovshenin <https://github.com/soulseekah>`_
+* `Matt Molyneaux <https://github.com/moggers87>`_
diff --git a/jsmin.egg-info/PKG-INFO b/jsmin.egg-info/PKG-INFO
deleted file mode 100644
index bbba187..0000000
--- a/jsmin.egg-info/PKG-INFO
+++ /dev/null
@@ -1,92 +0,0 @@
-Metadata-Version: 1.1
-Name: jsmin
-Version: 2.0.9
-Summary: JavaScript minifier.
-PLEASE UPDATE TO VERSION >= 2.0.6. Older versions have a serious bug related to comments.
-Home-page: https://bitbucket.org/dcs/jsmin/
-Author: Tikitu de Jager
-Author-email: tikitu+jsmin@logophile.org
-License: MIT License
-Description: =====
- jsmin
- =====
-
- JavaScript minifier.
-
- Usage
- =====
-
- .. code:: python
-
- from jsmin import jsmin
- with open('myfile.js') as js_file:
- minified = jsmin(js_file.read())
-
- You can run it as a commandline tool also::
-
- python -m jsmin myfile.js
-
- Where to get it
- ===============
-
- * install the package `from pypi <https://pypi.python.org/pypi/jsmin/>`_
- * get the latest release `from the stable branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/stable>`_
- * get the development version `from the default branch on bitbucket <https://bitbucket.org/dcs/jsmin/branch/default>`_
-
- Contributing
- ============
-
- `Issues <https://bitbucket.org/dcs/jsmin/issues>`_ and `Pull requests <https://bitbucket.org/dcs/jsmin/pull-requests>`_
- will be gratefully received on Bitbucket. Pull requests on github are great too, but the issue tracker lives on
- bitbucket.
-
- If possible, please make separate pull requests for tests and for code: tests will be committed on the stable branch
- (which tracks the latest released version) while code will go to default by, erm, default.
-
- Unless you request otherwise, your Bitbucket identity will be added to the contributor's list below; if you prefer a
- different name feel free to add it in your pull request instead. (If you prefer not to be mentioned you'll have to let
- the maintainer know somehow.)
-
- Build/test status
- =================
-
- Both default and stable branches are tested with Travis: https://travis-ci.org/tikitu/jsmin
-
- Stable (latest released version plus any new tests) is tested against CPython 2.6, 2.7, 3.2, and 3.3.
- Currently:
-
- .. image:: https://travis-ci.org/tikitu/jsmin.png?branch=ghstable
-
- If stable is failing that means there's a new test that fails on *the latest released version on pypi*, with no fix yet
- released.
-
- Default (development version, might be ahead of latest released version) is tested against CPython 2.6, 2.7, 3.2, and
- 3.3. Currently:
-
- .. image:: https://travis-ci.org/tikitu/jsmin.png?branch=master
-
- If default is failing don't use it, but as long as stable is passing the pypi release should be ok.
-
- Contributors (chronological commit order)
- =========================================
-
- * `Dave St.Germain <https://bitbucket.org/dcs>`_ (original author)
- * `Hans weltar <https://bitbucket.org/hansweltar>`_
- * `Tikitu de Jager <mailto:tikitu+jsmin@logophile.org>`_ (current maintainer)
- * https://bitbucket.org/rennat
-
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Software Development :: Pre-processors
-Classifier: Topic :: Text Processing :: Filters
diff --git a/jsmin.egg-info/SOURCES.txt b/jsmin.egg-info/SOURCES.txt
deleted file mode 100644
index 7131d58..0000000
--- a/jsmin.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-LICENSE.txt
-MANIFEST.in
-README.rst
-setup.cfg
-setup.py
-jsmin/__init__.py
-jsmin/__main__.py
-jsmin/test.py
-jsmin.egg-info/PKG-INFO
-jsmin.egg-info/SOURCES.txt
-jsmin.egg-info/dependency_links.txt
-jsmin.egg-info/top_level.txt \ No newline at end of file
diff --git a/jsmin.egg-info/dependency_links.txt b/jsmin.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/jsmin.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/jsmin.egg-info/top_level.txt b/jsmin.egg-info/top_level.txt
deleted file mode 100644
index 79abaa9..0000000
--- a/jsmin.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-jsmin
diff --git a/jsmin/__init__.py b/jsmin/__init__.py
index 23bed60..ed79e8e 100644
--- a/jsmin/__init__.py
+++ b/jsmin/__init__.py
@@ -37,10 +37,10 @@ else:
__all__ = ['jsmin', 'JavascriptMinify']
-__version__ = '2.0.9'
+__version__ = '2.2.0'
-def jsmin(js):
+def jsmin(js, **kwargs):
"""
returns a minified version of the javascript string
"""
@@ -55,7 +55,7 @@ def jsmin(js):
klass = io.StringIO
ins = klass(js)
outs = klass()
- JavascriptMinify(ins, outs).minify()
+ JavascriptMinify(ins, outs, **kwargs).minify()
return outs.getvalue()
@@ -65,9 +65,10 @@ class JavascriptMinify(object):
to an output stream
"""
- def __init__(self, instream=None, outstream=None):
+ def __init__(self, instream=None, outstream=None, quote_chars="'\""):
self.ins = instream
self.outs = outstream
+ self.quote_chars = quote_chars
def minify(self, instream=None, outstream=None):
if instream and outstream:
@@ -82,6 +83,8 @@ class JavascriptMinify(object):
if char in 'return':
self.return_buf += char
self.is_return = self.return_buf == 'return'
+ else:
+ self.return_buf = ''
self.outs.write(char)
if self.is_return:
self.return_buf = ''
@@ -90,70 +93,26 @@ class JavascriptMinify(object):
space_strings = "abcdefghijklmnopqrstuvwxyz"\
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\"
- starters, enders = '{[(+-', '}])+-"\''
- newlinestart_strings = starters + space_strings
- newlineend_strings = enders + space_strings
+ self.space_strings = space_strings
+ starters, enders = '{[(+-', '}])+-/' + self.quote_chars
+ newlinestart_strings = starters + space_strings + self.quote_chars
+ newlineend_strings = enders + space_strings + self.quote_chars
+ self.newlinestart_strings = newlinestart_strings
+ self.newlineend_strings = newlineend_strings
+
do_newline = False
do_space = False
escape_slash_count = 0
- doing_single_comment = False
- previous_before_comment = ''
- doing_multi_comment = False
- in_re = False
in_quote = ''
quote_buf = []
-
- previous = read(1)
- if previous == '\\':
- escape_slash_count += 1
+
+ previous = ';'
+ previous_non_space = ';'
next1 = read(1)
- if previous == '/':
- if next1 == '/':
- doing_single_comment = True
- elif next1 == '*':
- doing_multi_comment = True
- previous = next1
- next1 = read(1)
- else:
- write(previous)
- elif not previous:
- return
- elif previous >= '!':
- if previous in "'\"":
- in_quote = previous
- write(previous)
- previous_non_space = previous
- else:
- previous_non_space = ' '
- if not next1:
- return
- while 1:
+ while next1:
next2 = read(1)
- if not next2:
- last = next1.strip()
- if not (doing_single_comment or doing_multi_comment)\
- and last not in ('', '/'):
- if in_quote:
- write(''.join(quote_buf))
- write(last)
- break
- if doing_multi_comment:
- if next1 == '*' and next2 == '/':
- doing_multi_comment = False
- next2 = read(1)
- elif doing_single_comment:
- if next1 in '\r\n':
- doing_single_comment = False
- while next2 in '\r\n':
- next2 = read(1)
- if not next2:
- break
- if previous_before_comment in ')}]':
- do_newline = True
- elif previous_before_comment in space_strings:
- write('\n')
- elif in_quote:
+ if in_quote:
quote_buf.append(next1)
if next1 == in_quote:
@@ -167,19 +126,9 @@ class JavascriptMinify(object):
in_quote = ''
write(''.join(quote_buf))
elif next1 in '\r\n':
- if previous_non_space in newlineend_strings \
- or previous_non_space > '~':
- while 1:
- if next2 < '!':
- next2 = read(1)
- if not next2:
- break
- else:
- if next2 in newlinestart_strings \
- or next2 > '~' or next2 == '/':
- do_newline = True
- break
- elif next1 < '!' and not in_re:
+ next2, do_newline = self.newline(
+ previous_non_space, next2, do_newline)
+ elif next1 < '!':
if (previous_non_space in space_strings \
or previous_non_space > '~') \
and (next2 in space_strings or next2 > '~'):
@@ -193,41 +142,126 @@ class JavascriptMinify(object):
elif next1 == '/':
if do_space:
write(' ')
- if in_re:
- if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy':
- in_re = False
- write('/')
- elif next2 == '/':
- doing_single_comment = True
- previous_before_comment = previous_non_space
+ if next2 == '/':
+ # Line comment: treat it as a newline, but skip it
+ next2 = self.line_comment(next1, next2)
+ next1 = '\n'
+ next2, do_newline = self.newline(
+ previous_non_space, next2, do_newline)
elif next2 == '*':
- doing_multi_comment = True
- previous = next1
- next1 = next2
+ self.block_comment(next1, next2)
next2 = read(1)
+ if previous_non_space in space_strings:
+ do_space = True
+ next1 = previous
else:
- in_re = previous_non_space in '(,=:[?!&|' or self.is_return # literal regular expression
- write('/')
+ if previous_non_space in '{(,=:[?!&|;' or self.is_return:
+ self.regex_literal(next1, next2)
+ # hackish: after regex literal next1 is still /
+ # (it was the initial /, now it's the last /)
+ next2 = read(1)
+ else:
+ write('/')
else:
- if do_space:
- do_space = False
- write(' ')
if do_newline:
write('\n')
do_newline = False
-
+ do_space = False
+ if do_space:
+ do_space = False
+ write(' ')
+
write(next1)
- if not in_re and next1 in "'\"":
+ if next1 in self.quote_chars:
in_quote = next1
quote_buf = []
- previous = next1
- next1 = next2
+ if next1 >= '!':
+ previous_non_space = next1
- if previous >= '!':
- previous_non_space = previous
-
- if previous == '\\':
+ if next1 == '\\':
escape_slash_count += 1
else:
escape_slash_count = 0
+
+ previous = next1
+ next1 = next2
+
+ def regex_literal(self, next1, next2):
+ assert next1 == '/' # otherwise we should not be called!
+
+ self.return_buf = ''
+
+ read = self.ins.read
+ write = self.outs.write
+
+ in_char_class = False
+
+ write('/')
+
+ next = next2
+ while next != '/' or in_char_class:
+ write(next)
+ if next == '\\':
+ write(read(1)) # whatever is next is escaped
+ elif next == '[':
+ write(read(1)) # character class cannot be empty
+ in_char_class = True
+ elif next == ']':
+ in_char_class = False
+ next = read(1)
+
+ write('/')
+
+ def line_comment(self, next1, next2):
+ assert next1 == next2 == '/'
+
+ read = self.ins.read
+
+ while next1 and next1 not in '\r\n':
+ next1 = read(1)
+ while next1 and next1 in '\r\n':
+ next1 = read(1)
+
+ return next1
+
+ def block_comment(self, next1, next2):
+ assert next1 == '/'
+ assert next2 == '*'
+
+ read = self.ins.read
+
+ # Skip past first /* and avoid catching on /*/...*/
+ next1 = read(1)
+ next2 = read(1)
+
+ comment_buffer = '/*'
+ while next1 != '*' or next2 != '/':
+ comment_buffer += next1
+ next1 = next2
+ next2 = read(1)
+
+ if comment_buffer.startswith("/*!"):
+ # comment needs preserving
+ self.outs.write(comment_buffer)
+ self.outs.write("*/\n")
+
+
+ def newline(self, previous_non_space, next2, do_newline):
+ read = self.ins.read
+
+ if previous_non_space and (
+ previous_non_space in self.newlineend_strings
+ or previous_non_space > '~'):
+ while 1:
+ if next2 < '!':
+ next2 = read(1)
+ if not next2:
+ break
+ else:
+ if next2 in self.newlinestart_strings \
+ or next2 > '~' or next2 == '/':
+ do_newline = True
+ break
+
+ return next2, do_newline
diff --git a/jsmin/test.py b/jsmin/test.py
index 7171a5b..1638cf9 100644
--- a/jsmin/test.py
+++ b/jsmin/test.py
@@ -1,6 +1,6 @@
import unittest
import jsmin
-import sys
+
class JsTests(unittest.TestCase):
def _minify(self, js):
@@ -12,9 +12,9 @@ class JsTests(unittest.TestCase):
raise AssertionError
return True
- def assertMinified(self, js_input, expected):
- minified = jsmin.jsmin(js_input)
- assert minified == expected, "%r != %r" % (minified, expected)
+ def assertMinified(self, js_input, expected, **kwargs):
+ minified = jsmin.jsmin(js_input, **kwargs)
+ assert minified == expected, "\ngot: %r\nexp: %r" % (minified, expected)
def testQuoted(self):
js = r'''
@@ -52,10 +52,8 @@ class JsTests(unittest.TestCase):
}
//bye
'''
- expected = r"""
-if(Object.isFunction(Array.prototype.forEach))
-Array.prototype._each=Array.prototype.forEach;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(item,i){ function(){ foo; location='http://foo.com;';}"""
- # print expected
+ expected = r"""if(Object.isFunction(Array.prototype.forEach))
+Array.prototype._each=Array.prototype.forEach;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(item,i){function(){foo;location='http://foo.com;';}"""
self.assertMinified(js, expected)
def testEmpty(self):
@@ -116,13 +114,22 @@ another thing;"""
def testJustAComment(self):
self.assertMinified(' // a comment', '')
- def test_issue_10(self):
+ def test_issue_bitbucket_10(self):
js = '''
files = [{name: value.replace(/^.*\\\\/, '')}];
// comment
A
'''
- expected = '''files=[{name:value.replace(/^.*\\\\/,'')}]; A'''
+ expected = '''files=[{name:value.replace(/^.*\\\\/,'')}];A'''
+ self.assertMinified(js, expected)
+
+ def test_issue_bitbucket_10_without_semicolon(self):
+ js = '''
+ files = [{name: value.replace(/^.*\\\\/, '')}]
+ // comment
+ A
+ '''
+ expected = '''files=[{name:value.replace(/^.*\\\\/,'')}]\nA'''
self.assertMinified(js, expected)
def testRe(self):
@@ -154,7 +161,7 @@ another thing;"""
Element.cleanWhitespace(element);
"""
expected = r"""var options_for_droppable={overlap:options.overlap,containment:options.containment,tree:options.tree,hoverclass:options.hoverclass,onHover:Sortable.onHover}
-var options_for_tree={onHover:Sortable.onEmptyHover,overlap:options.overlap,containment:options.containment,hoverclass:options.hoverclass}
+var options_for_tree={onHover:Sortable.onEmptyHover,overlap:options.overlap,containment:options.containment,hoverclass:options.hoverclass}
Element.cleanWhitespace(element);"""
self.assertMinified(js, expected)
@@ -206,10 +213,22 @@ Element.cleanWhitespace(element);"""
onFailure: this.onFailure
});
"""
- expected = r"""onSuccess:function(transport){var js=transport.responseText.strip();if(!/^\[.*\]$/.test(js))
+ expected = r"""onSuccess:function(transport){var js=transport.responseText.strip();if(!/^\[.*\]$/.test(js))
throw'Server returned an invalid collection representation.';this._collection=eval(js);this.checkForExternalText();}.bind(this),onFailure:this.onFailure});"""
self.assertMinified(js, expected)
-
+ js_without_comment = r"""
+ onSuccess: function(transport) {
+ var js = transport.responseText.strip();
+ if (!/^\[.*\]$/.test(js))
+ throw 'Server returned an invalid collection representation.';
+ this._collection = eval(js);
+ this.checkForExternalText();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ """
+ self.assertMinified(js_without_comment, expected)
+
def testSpaceInRe(self):
js = r"""
num = num.replace(/ /g,'');
@@ -274,16 +293,22 @@ var foo = "hey";
}""", "{a:1,}")
def testCommentInObj2(self):
- self.assertMinified("{a: 1//comment\r\n}", "{a:1\n}")
+ self.assertMinified("{a: 1//comment\r\n}", "{a:1}")
def testImplicitSemicolon(self):
# return \n 1 is equivalent with return; 1
# so best make sure jsmin retains the newline
+ self.assertMinified("return\na", "return\na")
+
+ def test_explicit_semicolon(self):
self.assertMinified("return;//comment\r\na", "return;a")
def testImplicitSemicolon2(self):
+ self.assertMinified("return//comment...\r\nar", "return\nar")
+
+ def testImplicitSemicolon3(self):
self.assertMinified("return//comment...\r\na", "return\na")
-
+
def testSingleComment2(self):
self.assertMinified('x.replace(/\//, "_")// slash to underscore',
'x.replace(/\//,"_")')
@@ -300,7 +325,12 @@ var foo = "hey";
original = '''
return foo;//comment
return bar;'''
- expected = 'return foo; return bar;'
+ expected = 'return foo;return bar;'
+ self.assertMinified(original, expected)
+ original = '''
+ return foo
+ return bar;'''
+ expected = 'return foo\nreturn bar;'
self.assertMinified(original, expected)
def test_space_plus(self):
@@ -313,5 +343,267 @@ var foo = "hey";
expected = '"s"'
self.assertMinified(original, expected)
+ def test_space_with_regex_repeats(self):
+ original = '/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
+ self.assertMinified(original, original) # there should be nothing jsmin can do here
+
+ def test_space_with_regex_repeats_not_at_start(self):
+ original = 'aaa;/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
+ self.assertMinified(original, original) # there should be nothing jsmin can do here
+
+ def test_space_in_regex(self):
+ original = '/a (a)/.test("a")'
+ self.assertMinified(original, original)
+
+ def test_brackets_around_slashed_regex(self):
+ original = 'function a() { /\//.test("a") }'
+ expected = 'function a(){/\//.test("a")}'
+ self.assertMinified(original, expected)
+
+ def test_angular_1(self):
+ original = '''var /** holds major version number for IE or NaN for real browsers */
+ msie,
+ jqLite, // delay binding since jQuery could be loaded after us.'''
+ minified = jsmin.jsmin(original)
+ self.assertTrue('var\nmsie' in minified)
+
+ def test_angular_2(self):
+ original = 'var/* comment */msie;'
+ expected = 'var msie;'
+ self.assertMinified(original, expected)
+
+ def test_angular_3(self):
+ original = 'var /* comment */msie;'
+ expected = 'var msie;'
+ self.assertMinified(original, expected)
+
+ def test_angular_4(self):
+ original = 'var /* comment */ msie;'
+ expected = 'var msie;'
+ self.assertMinified(original, expected)
+
+ def test_angular_5(self):
+ original = 'a/b'
+ self.assertMinified(original, original)
+
+ def testBackticks(self):
+ original = '`test`'
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ original = '` test with leading whitespace`'
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ original = '`test with trailing whitespace `'
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ original = '''`test
+with a new line`'''
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ original = '''dumpAvStats: function(stats) {
+ var statsString = "";
+ if (stats.mozAvSyncDelay) {
+ statsString += `A/V sync: ${stats.mozAvSyncDelay} ms `;
+ }
+ if (stats.mozJitterBufferDelay) {
+ statsString += `Jitter-buffer delay: ${stats.mozJitterBufferDelay} ms`;
+ }
+
+ return React.DOM.div(null, statsString);'''
+ expected = 'dumpAvStats:function(stats){var statsString="";if(stats.mozAvSyncDelay){statsString+=`A/V sync: ${stats.mozAvSyncDelay} ms `;}\nif(stats.mozJitterBufferDelay){statsString+=`Jitter-buffer delay: ${stats.mozJitterBufferDelay} ms`;}\nreturn React.DOM.div(null,statsString);'
+ self.assertMinified(original, expected, quote_chars="'\"`")
+
+ def testBackticksExpressions(self):
+ original = '`Fifteen is ${a + b} and not ${2 * a + b}.`'
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ original = '''`Fifteen is ${a +
+b} and not ${2 * a + "b"}.`'''
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ def testBackticksTagged(self):
+ original = 'tag`Hello ${ a + b } world ${ a * b}`;'
+ self.assertMinified(original, original, quote_chars="'\"`")
+
+ def test_issue_bitbucket_16(self):
+ original = """
+ f = function() {
+ return /DataTree\/(.*)\//.exec(this._url)[1];
+ }
+ """
+ self.assertMinified(
+ original,
+ 'f=function(){return /DataTree\/(.*)\//.exec(this._url)[1];}')
+
+ def test_issue_bitbucket_17(self):
+ original = "// hi\n/^(get|post|head|put)$/i.test('POST')"
+ self.assertMinified(original,
+ "/^(get|post|head|put)$/i.test('POST')")
+
+ def test_issue_6(self):
+ original = '''
+ respond.regex = {
+ comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,
+ urls: 'whatever'
+ };
+ '''
+ expected = original.replace(' ', '').replace('\n', '')
+ self.assertMinified(original, expected)
+
+ def test_issue_9(self):
+ original = '\n'.join([
+ 'var a = \'hi\' // this is a comment',
+ 'var a = \'hi\' /* this is also a comment */',
+ 'console.log(1) // this is a comment',
+ 'console.log(1) /* this is also a comment */',
+ '1 // this is a comment',
+ '1 /* this is also a comment */',
+ '{} // this is a comment',
+ '{} /* this is also a comment */',
+ '"YOLO" /* this is a comment */',
+ '"YOLO" // this is a comment',
+ '(1 + 2) // comment',
+ '(1 + 2) /* yup still comment */',
+ 'var b'
+ ])
+ expected = '\n'.join([
+ 'var a=\'hi\'',
+ 'var a=\'hi\'',
+ 'console.log(1)',
+ 'console.log(1)',
+ '1',
+ '1',
+ '{}',
+ '{}',
+ '"YOLO"',
+ '"YOLO"',
+ '(1+2)',
+ '(1+2)',
+ 'var b'
+ ])
+ self.assertMinified(expected, expected)
+ self.assertMinified(original, expected)
+
+ def test_newline_between_strings(self):
+ self.assertMinified('"yolo"\n"loyo"', '"yolo"\n"loyo"')
+
+ def test_issue_10_comments_between_tokens(self):
+ self.assertMinified('var/* comment */a', 'var a')
+
+ def test_ends_with_string(self):
+ self.assertMinified('var s = "s"', 'var s="s"')
+
+ def test_short_comment(self):
+ self.assertMinified('a;/**/b', 'a;b')
+
+ def test_shorter_comment(self):
+ self.assertMinified('a;/*/*/b', 'a;b')
+
+ def test_block_comment_with_semicolon(self):
+ self.assertMinified('a;/**/\nb', 'a;b')
+
+ def test_block_comment_With_implicit_semicolon(self):
+ self.assertMinified('a/**/\nvar b', 'a\nvar b')
+
+ def test_issue_9_single_comments(self):
+ original = '''
+ var a = "hello" // this is a comment
+ a += " world"
+ '''
+ self.assertMinified(original, 'var a="hello"\na+=" world"')
+
+ def test_issue_9_multi_comments(self):
+ original = '''
+ var a = "hello" /* this is a comment */
+ a += " world"
+ '''
+ self.assertMinified(original, 'var a="hello"\na+=" world"')
+
+ def test_issue_12_re_nl_if(self):
+ original = '''
+ var re = /\d{4}/
+ if (1) { console.log(2); }'''
+ self.assertMinified(
+ original, 'var re=/\d{4}/\nif(1){console.log(2);}')
+
+ def test_issue_12_re_nl_other(self):
+ original = '''
+ var re = /\d{4}/
+ g = 10'''
+ self.assertMinified(original , 'var re=/\d{4}/\ng=10')
+
+ def test_preserve_copyright(self):
+ original = '''
+ function this() {
+ /*! Copyright year person */
+ console.log('hello!');
+ }
+
+ /*! Copyright blah blah
+ *
+ * Some other text
+ */
+
+ var a;
+ '''
+ expected = """function this(){/*! Copyright year person */
+console.log('hello!');}/*! Copyright blah blah
+ *
+ * Some other text
+ */\n\nvar a;"""
+ self.assertMinified(original, expected)
+
+
+class RegexTests(unittest.TestCase):
+
+ def regex_recognise(self, js):
+ if not jsmin.is_3:
+ if jsmin.cStringIO and not isinstance(js, unicode):
+ # strings can use cStringIO for a 3x performance
+ # improvement, but unicode (in python2) cannot
+ klass = jsmin.cStringIO.StringIO
+ else:
+ klass = jsmin.StringIO.StringIO
+ else:
+ klass = jsmin.io.StringIO
+ ins = klass(js[2:])
+ outs = klass()
+ jsmin.JavascriptMinify(ins, outs).regex_literal(js[0], js[1])
+ return outs.getvalue()
+
+ def assert_regex(self, js_input, expected):
+ assert js_input[0] == '/' # otherwise we should not be testing!
+ recognised = self.regex_recognise(js_input)
+ assert recognised == expected, "\n in: %r\ngot: %r\nexp: %r" % (js_input, recognised, expected)
+
+ def test_simple(self):
+ self.assert_regex('/123/g', '/123/')
+
+ def test_character_class(self):
+ self.assert_regex('/a[0-9]b/g', '/a[0-9]b/')
+
+ def test_character_class_with_slash(self):
+ self.assert_regex('/a[/]b/g', '/a[/]b/')
+
+ def test_escaped_forward_slash(self):
+ self.assert_regex(r'/a\/b/g', r'/a\/b/')
+
+ def test_escaped_back_slash(self):
+ self.assert_regex(r'/a\\/g', r'/a\\/')
+
+ def test_empty_character_class(self):
+ # This one is subtle: an empty character class is not allowed, afaics
+ # from http://regexpal.com/ Chrome Version 44.0.2403.155 (64-bit) Mac
+ # so this char class is interpreted as containing ]/ *not* as char
+ # class [] followed by end-of-regex /.
+ self.assert_regex('/a[]/]b/g', '/a[]/]b/')
+
+ def test_precedence_of_parens(self):
+ # judging from
+ # http://regexpal.com/ Chrome Version 44.0.2403.155 (64-bit) Mac
+ # () have lower precedence than []
+ self.assert_regex('/a([)])b/g', '/a([)])b/')
+ self.assert_regex('/a[(]b/g', '/a[(]b/')
+
if __name__ == '__main__':
unittest.main()
diff --git a/setup.py b/setup.py
index 8fff566..2ac0645 100644
--- a/setup.py
+++ b/setup.py
@@ -9,17 +9,22 @@ extra = {}
if sys.version_info >= (3,0):
extra['use_2to3'] = True
+
+def long_description():
+ return open('README.rst').read() + '\n' + open('CHANGELOG.txt').read()
+
+
setup(
name="jsmin",
version=re.search(r'__version__ = ["\']([^"\']+)', open('jsmin/__init__.py').read()).group(1),
packages=['jsmin'],
- description='JavaScript minifier.\nPLEASE UPDATE TO VERSION >= 2.0.6. Older versions have a serious bug related to comments.',
- long_description=open('README.rst').read(),
+ description='JavaScript minifier.',
+ long_description=long_description(),
author='Dave St.Germain',
author_email='dave@st.germa.in',
maintainer='Tikitu de Jager',
maintainer_email='tikitu+jsmin@logophile.org',
- test_suite='jsmin.test.JsTests',
+ test_suite='jsmin.test',
license='MIT License',
url='https://bitbucket.org/dcs/jsmin/',
classifiers=[