[javascriptlint-commit] SF.net SVN: javascriptlint: [147] trunk
Status: Beta
Brought to you by:
matthiasmiller
|
From: <mat...@us...> - 2008-03-01 17:21:12
|
Revision: 147
http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=147&view=rev
Author: matthiasmiller
Date: 2008-03-01 09:21:01 -0800 (Sat, 01 Mar 2008)
Log Message:
-----------
initial import of pyjsl
Added Paths:
-----------
trunk/COPYING
trunk/INSTALL
trunk/Makefile.SpiderMonkey
trunk/TODO
trunk/jsl.py
trunk/pyjsl/
trunk/pyjsl/__init__.py
trunk/pyjsl/conf.py
trunk/pyjsl/lint.py
trunk/pyjsl/parse.py
trunk/pyjsl/util.py
trunk/pyjsl/visitation.py
trunk/pyjsl/warnings.py
trunk/pyspidermonkey/
trunk/pyspidermonkey/pyspidermonkey.c
trunk/pyspidermonkey/tokens.tbl
trunk/setup.py
trunk/test.py
Property Changed:
----------------
trunk/
Property changes on: trunk
___________________________________________________________________
Name: svn:ignore
+ dist
build
*.pyc
Added: trunk/COPYING
===================================================================
--- trunk/COPYING (rev 0)
+++ trunk/COPYING 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Added: trunk/INSTALL
===================================================================
--- trunk/INSTALL (rev 0)
+++ trunk/INSTALL 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,14 @@
+
+BUILDING FROM THE SUBVERSION TRUNK
+* Windows Prequisites:
+ * Visual Studio 2003
+ * Python
+ * MozillaBuild (http://developer.mozilla.org/en/docs/Windows_Build_Prerequisites)
+
+ Launch the MozillaBuild 7.1 batch file. (You may have to run this as an Administrator
+ on Windows Vista.) Run the commands in that shell.
+
+On all platforms:
+ $ make -f Makefile.SpiderMonkey
+ $ python setup.py build
+
Added: trunk/Makefile.SpiderMonkey
===================================================================
--- trunk/Makefile.SpiderMonkey (rev 0)
+++ trunk/Makefile.SpiderMonkey 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,67 @@
+
+SPIDERMONKEY_SRC=spidermonkey/src
+
+# Load the SpiderMonkey config to find the OS define
+# Also use this for the SO_SUFFIX
+BUILD_OPT=1
+DEPTH=$(SPIDERMONKEY_SRC)
+include $(SPIDERMONKEY_SRC)/config.mk
+SPIDERMONKEY_OS=$(firstword $(patsubst -D%, %, $(filter -DXP_%, $(OS_CFLAGS))))
+
+ifdef USE_MSVC
+JS_LIB=js32.lib
+else
+JS_LIB=libjs.a
+endif
+
+BUILD_DIR=build/spidermonkey
+
+# Use a dynamically-created makefile to determine the distutils output dir
+DISTUTILS_DIR_MAKEFILE=$(BUILD_DIR)/Makefile-distutils
+include $(DISTUTILS_DIR_MAKEFILE)
+
+ORIG_LIB=$(SPIDERMONKEY_SRC)/$(OBJDIR)/$(JS_LIB)
+COPY_LIB=$(BUILD_DIR)/$(JS_LIB)
+ORIG_DLL=$(SPIDERMONKEY_SRC)/$(OBJDIR)/js32.dll
+COPY_DLL=$(DISTUTILS_DIR)/js32.dll
+OS_HEADER=$(BUILD_DIR)/js_operating_system.h
+ORIG_JSAUTOCFG_H=$(SPIDERMONKEY_SRC)/$(OBJDIR)/jsautocfg.h
+COPY_JSAUTOCFG_H=$(BUILD_DIR)/jsautocfg.h
+
+ALL_TARGETS=$(COPY_LIB) $(OS_HEADER)
+ifndef PREBUILT_CPUCFG
+ALL_TARGETS+=$(COPY_JSAUTOCFG_H)
+endif
+
+ifeq ($(SPIDERMONKEY_OS),XP_WIN)
+ALL_TARGETS+=$(COPY_DLL)
+endif
+
+all: $(ALL_TARGETS)
+
+clean:
+ make -f Makefile.ref -C $(SPIDERMONKEY_SRC) BUILD_OPT=$(BUILD_OPT) clean
+ rm $(ORIG_LIB)
+ rm -R $(BUILD_DIR)
+
+$(DISTUTILS_DIR_MAKEFILE): Makefile.SpiderMonkey $(BUILD_DIR)
+ python -c "import setup; print 'DISTUTILS_DIR='+setup.get_lib_path()" >> $(DISTUTILS_DIR_MAKEFILE)
+
+$(BUILD_DIR):
+ mkdir -p $(BUILD_DIR)
+
+$(COPY_LIB): $(BUILD_DIR) $(ORIG_LIB)
+ cp $(ORIG_LIB) $(COPY_LIB)
+
+$(COPY_DLL): $(BUILD_DIR) $(ORIG_LIB)
+ cp $(ORIG_DLL) $(COPY_DLL)
+
+$(OS_HEADER): $(BUILD_DIR)
+ echo "#define $(SPIDERMONKEY_OS)" > $(OS_HEADER)
+
+$(COPY_JSAUTOCFG_H): $(ORIG_JSAUTOCFG_H)
+ cp $(ORIG_JSAUTOCFG_H) $(COPY_JSAUTOCFG_H)
+
+$(ORIG_LIB):
+ make -f Makefile.ref -C $(SPIDERMONKEY_SRC) BUILD_OPT=$(BUILD_OPT)
+
Added: trunk/TODO
===================================================================
--- trunk/TODO (rev 0)
+++ trunk/TODO 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,13 @@
+
+> implement conf file
+
+> support HTML parsing
+
+> support JScript extensions
+
+> implement semicolons warning
+
+> implement line break warning
+
+> add test for syntax error
+
Added: trunk/jsl.py
===================================================================
--- trunk/jsl.py (rev 0)
+++ trunk/jsl.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,147 @@
+#!/usr/bin/python
+import codecs
+import getopt
+import glob
+import os
+import sys
+import unittest
+
+try:
+ import setup
+except ImportError:
+ pass
+else:
+ sys.path.append(setup.get_lib_path())
+
+import pyjsl.conf
+import pyjsl.parse
+import pyjsl.util
+import test
+
+_lint_results = {
+ 'warnings': 0,
+ 'errors': 0
+}
+
+def get_test_files():
+ # Get a list of test files.
+ dir_ = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tests')
+
+ all_files = []
+ for root, dirs, files in os.walk(dir_):
+ all_files += [os.path.join(dir_, root, file) for file in files]
+ if '.svn' in dirs:
+ dirs.remove('.svn')
+ # TODO
+ if 'conf' in dirs:
+ dirs.remove('conf')
+ all_files.sort()
+ return all_files
+
+def run_tests():
+ for file in get_test_files():
+ if file.endswith('.htm') or file.endswith('.html'):
+ continue #TODO
+ elif file.endswith('.js'):
+ print file
+ try:
+ test.run(file)
+ except test.TestError, error:
+ print error
+
+def _dump(paths):
+ for path in paths:
+ script = pyjsl.util.readfile(path)
+ pyjsl.parse.dump_tree(script)
+
+def _lint(paths, conf):
+ def lint_error(path, line, col, errname):
+ _lint_results['warnings'] = _lint_results['warnings'] + 1
+ print '%s(%i): %s' % (path, line, errname)
+ pyjsl.lint.lint_files(paths, lint_error, conf=conf)
+
+def _resolve_paths(path, recurse):
+ if os.path.isfile(path):
+ return [path]
+ elif os.path.isdir(path):
+ dir = path
+ pattern = '*'
+ else:
+ dir, pattern = os.path.split(path)
+
+ # Build a list of directories
+ dirs = [dir]
+ if recurse:
+ for cur_root, cur_dirs, cur_files in os.walk(dir):
+ for name in cur_dirs:
+ dirs.append(os.path.join(cur_root, name))
+
+ # Glob all files.
+ paths = []
+ for dir in dirs:
+ paths.extend(glob.glob(os.path.join(dir, pattern)))
+ return paths
+
+def profile_enabled(func, *args, **kwargs):
+ import tempfile
+ import hotshot
+ import hotshot.stats
+ handle, filename = tempfile.mkstemp()
+ profile = hotshot.Profile(filename)
+ profile.runcall(func, *args, **kwargs)
+ profile.close()
+ stats = hotshot.stats.load(filename)
+ stats = stats.sort_stats("time")
+ stats.print_stats()
+def profile_disabled(func, *args, **kwargs):
+ func(*args, **kwargs)
+
+def usage():
+ print """
+Usage:
+ jsl [files]
+ --help (-h) print this help
+ --test (-t) run tests
+ --dump= dump this script
+"""
+
+if __name__ == '__main__':
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'ht:v', ['conf=', 'help', 'test', 'dump', 'unittest', 'profile'])
+ except getopt.GetoptError:
+ usage()
+ sys.exit(2)
+
+ dump = False
+ conf = pyjsl.conf.Conf()
+ profile_func = profile_disabled
+ for opt, val in opts:
+ if opt in ('-h', '--help'):
+ usage()
+ sys.exit()
+ if opt in ('--dump',):
+ dump = True
+ if opt in ('-t', '--test'):
+ profile_func(run_tests)
+ if opt in ('--unittest',):
+ unittest.main(pyjsl.parse, argv=sys.argv[:1])
+ if opt in ('--profile',):
+ profile_func = profile_enabled
+ if opt in ('--conf',):
+ conf.loadfile(val)
+
+ paths = []
+ for recurse, path in conf['paths']:
+ paths.extend(_resolve_paths(path, recurse))
+ for arg in args:
+ paths.extend(_resolve_paths(arg, False))
+ if dump:
+ profile_func(_dump, paths)
+ else:
+ profile_func(_lint, paths, conf)
+
+ if _lint_results['errors']:
+ sys.exit(3)
+ if _lint_results['warnings']:
+ sys.exit(1)
+
Property changes on: trunk/jsl.py
___________________________________________________________________
Name: svn:executable
+ *
Property changes on: trunk/pyjsl
___________________________________________________________________
Name: svn:ignore
+ *.pyc
Added: trunk/pyjsl/__init__.py
===================================================================
Added: trunk/pyjsl/conf.py
===================================================================
--- trunk/pyjsl/conf.py (rev 0)
+++ trunk/pyjsl/conf.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,134 @@
+import os
+
+import warnings
+
+class ConfError(Exception):
+ def __init__(self, error):
+ Exception.__init__(error)
+ self.lineno = None
+ self.path = None
+
+class Setting():
+ wants_parm = False
+ wants_dir = False
+
+class BooleanSetting(Setting):
+ wants_parm = False
+ def __init__(self, default):
+ self.value = default
+ def load(self, enabled):
+ self.value = enabled
+
+class StringSetting(Setting):
+ wants_parm = True
+ def __init__(self, default):
+ self.value = default
+ def load(self, enabled, parm):
+ if not enabled:
+ raise ConfError, 'Expected +.'
+ self.value = parm
+
+class DeclareSetting(Setting):
+ wants_parm = True
+ def __init__(self):
+ self.value = []
+ def load(self, enabled, parm):
+ if not enabled:
+ raise ConfError, 'Expected +.'
+ self.value.append(parm)
+
+class ProcessSetting(Setting):
+ wants_parm = True
+ wants_dir = True
+ def __init__(self, recurse_setting):
+ self.value = []
+ self._recurse = recurse_setting
+ def load(self, enabled, parm, dir):
+ if dir:
+ parm = os.path.join(dir, parm)
+ self.value.append((self._recurse.value, parm))
+
+class Conf():
+ def __init__(self):
+ recurse = BooleanSetting(False)
+ self._settings = {
+ 'recurse': recurse,
+ 'show_context': BooleanSetting(False),
+ 'output-format': StringSetting('TODO'),
+ 'lambda_assign_requires_semicolon': BooleanSetting(False),
+ 'legacy_control_comments': BooleanSetting(True),
+ 'jscript_function_extensions': BooleanSetting(False),
+ 'always_use_option_explicit': BooleanSetting(False),
+ 'define': DeclareSetting(),
+ 'context': BooleanSetting(False),
+ 'process': ProcessSetting(recurse),
+ # SpiderMonkey warnings
+ 'no_return_value': BooleanSetting(True),
+ 'equal_as_assign': BooleanSetting(True),
+ 'anon_no_return_value': BooleanSetting(True)
+ }
+ for klass in warnings.klasses:
+ self._settings[klass.__name__] = BooleanSetting(True)
+ self.loadline('-block_without_braces')
+
+ def loadfile(self, path):
+ path = os.path.abspath(path)
+ conf = open(path, 'r').read()
+ try:
+ self.loadtext(conf, dir=os.path.dirname(path))
+ except ConfError, error:
+ error.path = path
+ raise
+
+ def loadtext(self, conf, dir=None):
+ lines = conf.splitlines()
+ for lineno in range(0, len(lines)):
+ try:
+ self.loadline(lines[lineno], dir)
+ except ConfError, error:
+ error.lineno = lineno
+ raise
+
+ def loadline(self, line, dir=None):
+ assert not '\r' in line
+ assert not '\n' in line
+
+ # Allow comments
+ line = line.partition('#')[0]
+ line = line.rstrip()
+ if not line:
+ return
+
+ # Parse the +/-
+ if line.startswith('+'):
+ enabled = True
+ elif line.startswith('-'):
+ enabled = False
+ else:
+ raise ConfError, 'Expected + or -.'
+ line = line[1:]
+
+ # Parse the key/parms
+ name = line.split()[0].lower()
+ parm = line[len(name):].lstrip()
+
+ # Load the setting
+ setting = self._settings[name]
+ args = {
+ 'enabled': enabled
+ }
+ if setting.wants_parm:
+ args['parm'] = parm
+ elif parm:
+ raise ConfError, 'The %s setting does not expect a parameter.' % name
+ if setting.wants_dir:
+ args['dir'] = dir
+ setting.load(**args)
+
+ def __getitem__(self, name):
+ if name == 'paths':
+ name = 'process'
+ elif name == 'declarations':
+ name = 'define'
+ return self._settings[name].value
+
Added: trunk/pyjsl/lint.py
===================================================================
--- trunk/pyjsl/lint.py (rev 0)
+++ trunk/pyjsl/lint.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,288 @@
+#!/usr/bin/python
+import os.path
+import re
+
+import conf
+import parse
+import visitation
+import warnings
+import util
+
+_newline_kinds = (
+ 'eof', 'comma', 'dot', 'semi', 'colon', 'lc', 'rc', 'lp', 'rb', 'assign',
+ 'relop', 'hook', 'plus', 'minus', 'star', 'divop', 'eqop', 'shop', 'or',
+ 'and', 'bitor', 'bitxor', 'bitand', 'else', 'try'
+)
+
+_globals = frozenset([
+ 'Array', 'Boolean', 'Math', 'Number', 'String', 'RegExp', 'Script', 'Date',
+ 'isNaN', 'isFinite', 'parseFloat', 'parseInt',
+ 'eval', 'NaN', 'Infinity',
+ 'escape', 'unescape', 'uneval',
+ 'decodeURI', 'encodeURI', 'decodeURIComponent', 'encodeURIComponent',
+ 'Function', 'Object',
+ 'Error', 'InternalError', 'EvalError', 'RangeError', 'ReferenceError',
+ 'SyntaxError', 'TypeError', 'URIError',
+ 'arguments', 'undefined'
+])
+
+_identifier = re.compile('^[A-Za-z_$][A-Za-z0-9_$]*$')
+
+def _find_function(node):
+ while node and node.kind != 'function':
+ node = node.parent
+ return node
+
+def _find_functions(node):
+ functions = []
+ while node:
+ if node.kind == 'function':
+ functions.append(node)
+ node = node.parent
+ return functions
+
+def _parse_control_comment(comment):
+ """ Returns None or (keyword, parms) """
+ if comment.atom.lower().startswith('jsl:'):
+ control_comment = comment.atom[4:]
+ elif comment.atom.startswith('@') and comment.atom.endswith('@'):
+ control_comment = comment.atom[1:-1]
+ else:
+ return None
+
+ control_comments = {
+ 'ignoreall': (False),
+ 'ignore': (False),
+ 'end': (False),
+ 'option explicit': (False),
+ 'import': (True),
+ 'fallthru': (False),
+ 'pass': (False),
+ 'declare': (True)
+ }
+ if control_comment.lower() in control_comments:
+ keyword = control_comment.lower()
+ else:
+ keyword = control_comment.lower().split()[0]
+
+ parms = control_comment[len(keyword):].strip()
+ return (comment, keyword, parms)
+
+class Scope():
+ def __init__(self, node):
+ self._is_with_scope = node.kind == 'with'
+ self._parent = None
+ self._kids = []
+ self._identifiers = {}
+ self._references = []
+ self._node = node
+ def add_scope(self, node):
+ self._kids.append(Scope(node))
+ self._kids[-1]._parent = self
+ if self._is_with_scope:
+ self._kids[-1]._is_with_scope = True
+ return self._kids[-1]
+ def add_declaration(self, name, node):
+ if not self._is_with_scope:
+ self._identifiers[name] = node
+ def add_reference(self, name, node):
+ if not self._is_with_scope:
+ self._references.append((name, node))
+ def get_identifier(self, name):
+ if name in self._identifiers:
+ return self._identifiers[name]
+ else:
+ return None
+ def get_identifiers(self):
+ "returns a list of names"
+ return self._identifiers.keys()
+ def resolve_identifier(self, name):
+ if name in self._identifiers:
+ return self, self._identifiers[name]
+ if self._parent:
+ return self._parent.resolve_identifier(name)
+ return None
+ def get_undeclared_identifiers(self):
+ identifiers = []
+ for child in self._kids:
+ identifiers += child.get_undeclared_identifiers()
+ for name, node in self._references:
+ if not self.resolve_identifier(name):
+ identifiers.append(node)
+ return identifiers
+ def find_scope(self, node):
+ for kid in self._kids:
+ scope = kid.find_scope(node)
+ if scope:
+ return scope
+
+ # Always add it to the outer scope.
+ if not self._parent or \
+ (node.start_pos() >= self._node.start_pos() and \
+ node.end_pos() <= self._node.end_pos()):
+ return self
+
+ return None
+
+def lint_files(paths, lint_error, conf=conf.Conf()):
+ def lint_file(path):
+ def import_script(import_path):
+ import_path = os.path.join(os.path.dirname(path), import_path)
+ return lint_file(import_path)
+ def _lint_error(*args):
+ return lint_error(normpath, *args)
+
+ normpath = util.normpath(path)
+ if not normpath in lint_cache:
+ lint_cache[normpath] = {}
+ script = util.readfile(path)
+ print normpath
+ _lint_script(script, lint_cache[normpath], _lint_error, conf, import_script)
+ return lint_cache[normpath]
+
+ lint_cache = {}
+ for path in paths:
+ lint_file(path)
+
+def _lint_script(script, script_cache, lint_error, conf, import_callback):
+ def parse_error(row, col, msg):
+ if not msg in ('redeclared_var', 'var_hides_arg'):
+ parse_errors.append((parse.NodePos(row, col), msg))
+
+ def report(node, errname):
+ _report(node.start_pos(), errname, True)
+
+ def _report(pos, errname, require_key):
+ try:
+ if not conf[errname]:
+ return
+ except KeyError, err:
+ if require_key:
+ raise
+
+ for start, end in ignores:
+ if pos >= start and pos <= end:
+ return
+
+ return lint_error(pos.line, pos.col, errname)
+
+ parse_errors = []
+ root, comments = parse.parse(script, parse_error)
+ ignores = []
+ start_ignore = None
+ declares = []
+ import_paths = []
+ for comment in comments:
+ cc = _parse_control_comment(comment)
+ if cc:
+ node, keyword, parms = cc
+ if keyword == 'declare':
+ if not _identifier.match(parms):
+ report(node, 'jsl_cc_not_understood')
+ else:
+ declares.append((parms, node))
+ elif keyword == 'ignore':
+ if start_ignore:
+ report(node, 'mismatch_ctrl_comments')
+ else:
+ start_ignore = node
+ elif keyword == 'end':
+ if start_ignore:
+ ignores.append((start_ignore.start_pos(), node.end_pos()))
+ start_ignore = None
+ else:
+ report(node, 'mismatch_ctrl_comments')
+ elif keyword == 'import':
+ if not parms:
+ report(node, 'jsl_cc_not_understood')
+ else:
+ import_paths.append(parms)
+ else:
+ if comment.opcode == 'c_comment':
+ if '/*' in comment.atom or comment.atom.endswith('/'):
+ report(comment, 'nested_comment')
+ if comment.atom.lower().startswith('jsl:'):
+ report(comment, 'jsl_cc_not_understood')
+ elif comment.atom.startswith('@'):
+ report(comment, 'legacy_cc_not_understood')
+ if start_ignore:
+ report(start_ignore, 'mismatch_ctrl_comments')
+
+ # Wait to report parse errors until loading jsl:ignore directives.
+ for pos, msg in parse_errors:
+ _report(pos, msg, False)
+
+ visitors = visitation.make_visitors(warnings.klasses)
+
+ assert not script_cache
+ imports = script_cache['imports'] = set()
+ scope = script_cache['scope'] = Scope(root)
+
+ # kickoff!
+ _lint_node(root, visitors, report, scope)
+
+ # Process imports by copying global declarations into the universal scope.
+ imports |= set(conf['declarations'])
+ imports |= _globals
+ for path in import_paths:
+ cache = import_callback(path)
+ imports |= cache['imports']
+ imports |= set(cache['scope'].get_identifiers())
+
+ for name, node in declares:
+ declare_scope = scope.find_scope(node)
+ if declare_scope.get_identifier(name):
+ report(node, 'redeclared_var')
+ else:
+ declare_scope.add_declaration(name, node)
+
+ for node in scope.get_undeclared_identifiers():
+ if not node.atom in imports:
+ report(node, 'undeclared_identifier')
+
+def _lint_node(node, visitors, report, scope):
+
+ def warn_or_declare(name, node):
+ other = scope.get_identifier(name)
+ if other and other.kind == 'function' and name in other.fn_args:
+ report(node, 'var_hides_arg')
+ elif other:
+ report(node, 'redeclared_var')
+ else:
+ scope.add_declaration(name, node)
+
+ # Let the visitors warn.
+ for kind in (node.kind, '%s:%s' % (node.kind, node.opcode)):
+ if kind in visitors:
+ for visitor in visitors[kind]:
+ warning_node = visitor(node)
+ if warning_node:
+ report(warning_node, visitor.im_class.__name__)
+
+ if node.kind == 'name':
+ if node.node_index == 0 and node.parent.kind == 'colon' and node.parent.parent.kind == 'rc':
+ pass # left side of object literal
+ elif node.parent.kind == 'catch':
+ scope.add_declaration(node.atom, node)
+ else:
+ scope.add_reference(node.atom, node)
+
+ # Push function identifiers
+ if node.kind == 'function':
+ if node.fn_name:
+ warn_or_declare(node.fn_name, node)
+ scope = scope.add_scope(node)
+ for var_name in node.fn_args:
+ scope.add_declaration(var_name, node)
+ elif node.kind == 'lexicalscope':
+ scope = scope.add_scope(node)
+ elif node.kind == 'with':
+ scope = scope.add_scope(node)
+
+ if node.parent and node.parent.kind == 'var':
+ warn_or_declare(node.atom, node)
+
+ for child in node.kids:
+ if child:
+ _lint_node(child, visitors, report, scope)
+
Added: trunk/pyjsl/parse.py
===================================================================
--- trunk/pyjsl/parse.py (rev 0)
+++ trunk/pyjsl/parse.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+""" Parses a script into nodes. """
+import bisect
+import re
+import unittest
+
+import pyspidermonkey
+
+class NodePos():
+ def __init__(self, line, col):
+ self.line = line
+ self.col = col
+ def __cmp__(self, other):
+ if self.line < other.line:
+ return -1
+ if self.line > other.line:
+ return 1
+ if self.col < other.col:
+ return -1
+ if self.col > other.col:
+ return 1
+ return 0
+ def __repr__(self):
+ return '(line %i, col %i)' % (self.line+1, self.col+1)
+
+class NodePositions():
+ " Given a string, allows [x] lookups for NodePos line and column numbers."
+ def __init__(self, text):
+ # Find the length of each line and incrementally sum all of the lengths
+ # to determine the ending position of each line.
+ self._lines = text.splitlines(True)
+ lines = [0] + [len(x) for x in self._lines]
+ for x in range(1, len(lines)):
+ lines[x] += lines[x-1]
+ self._line_offsets = lines
+ def from_offset(self, offset):
+ line = bisect.bisect(self._line_offsets, offset)-1
+ col = offset - self._line_offsets[line]
+ return NodePos(line, col)
+ def to_offset(self, pos):
+ offset = self._line_offsets[pos.line] + pos.col
+ assert offset <= self._line_offsets[pos.line+1] # out-of-bounds col num
+ return offset
+ def text(self, start, end):
+ assert start <= end
+ # Trim the ending first in case it's a single line.
+ lines = self._lines[start.line:end.line+1]
+ lines[-1] = lines[-1][:end.col+1]
+ lines[0] = lines[0][start.col:]
+ return ''.join(lines)
+
+class NodeRanges():
+ def __init__(self):
+ self._offsets = []
+ def add(self, start, end):
+ i = bisect.bisect_left(self._offsets, start)
+ if i % 2 == 1:
+ i -= 1
+ start = self._offsets[i]
+
+ end = end + 1
+ j = bisect.bisect_left(self._offsets, end)
+ if j % 2 == 1:
+ end = self._offsets[j]
+ j += 1
+
+ self._offsets[i:j] = [start,end]
+ def has(self, pos):
+ return bisect.bisect_right(self._offsets, pos) % 2 == 1
+
+class _Node():
+ def __init__(self, kwargs):
+ def _to_node(kid):
+ if kid:
+ return _Node(kid)
+ kwargs['type'] = kwargs['type'].lower()
+ self.kind = kwargs['type']
+ assert kwargs['opcode'].startswith('JSOP_')
+ kwargs['opcode'] = kwargs['opcode'][5:].lower()
+ self.opcode = kwargs['opcode']
+ self.kids = tuple([_to_node(kid) for kid in kwargs['kids']])
+ for kid in self.kids:
+ if kid:
+ kid.parent = self
+ if 'atom' in kwargs:
+ self.atom = kwargs['atom']
+ if 'dval' in kwargs:
+ self.dval = kwargs['dval']
+ if 'fn_name' in kwargs:
+ self.fn_name = kwargs['fn_name']
+ if 'fn_args' in kwargs:
+ self.fn_args = kwargs['fn_args']
+ if 'end_comma' in kwargs:
+ self.end_comma = kwargs['end_comma']
+ self.args = kwargs
+ self.node_index = kwargs['node_index']
+ self.parent = None
+ self.start_line = kwargs['start_row']
+ self._start_pos = None
+ self._end_pos = None
+
+ def add_child(self, node):
+ if node:
+ node.node_index = len(self.kids)
+ node.parent = self
+ self.kids.append(node)
+
+ def start_pos(self):
+ self._start_pos = self._start_pos or \
+ NodePos(self.args['start_row'], self.args['start_col'])
+ return self._start_pos
+
+ def end_pos(self):
+ self._end_pos = self._end_pos or \
+ NodePos(self.args['end_row'], self.args['end_col'])
+ return self._end_pos
+
+ def __repr__(self):
+ kind = self.kind
+ if not kind:
+ kind = '(none)'
+ return '%s>%s' % (kind, str(self.kids))
+
+ def is_equivalent(self, other, are_functions_equiv=False):
+ if not other:
+ return False
+
+ # Bail out for functions
+ if not are_functions_equiv:
+ if self.kind == 'function':
+ return False
+ if self.kind == 'lp' and self.opcode == 'call':
+ return False
+
+ if self.kind != other.kind:
+ return False
+ if self.opcode != other.opcode:
+ return False
+
+ # Check atoms on names, properties, and string constants
+ if self.kind in ('name', 'dot', 'string') and self.atom != other.atom:
+ return False
+
+ # Check values on numbers
+ if self.kind == 'number' and self.dval != other.dval:
+ return False
+
+ # Compare child nodes
+ if len(self.kids) != len(other.kids):
+ return False
+ for i in range(0, len(self.kids)):
+ # Watch for dead nodes
+ if not self.kids[i]:
+ if not other.kids[i]: return True
+ else: return False
+ if not self.kids[i].is_equivalent(other.kids[i]):
+ return False
+
+ return True
+
+def _parse_comments(script, root, node_positions, ignore_ranges):
+ pos = 0
+ single_line_re = r"//[^\r\n]*"
+ multi_line_re = r"/\*(.*?)\*/"
+ full_re = "(%s)|(%s)" % (single_line_re, multi_line_re)
+ comment_re = re.compile(full_re, re.DOTALL)
+
+ comments = []
+ while True:
+ match = comment_re.search(script, pos)
+ if not match:
+ return comments
+
+ # Get the comment text
+ comment_text = script[match.start():match.end()]
+ if comment_text.startswith('/*'):
+ comment_text = comment_text[2:-2]
+ opcode = 'JSOP_C_COMMENT'
+ else:
+ comment_text = comment_text[2:]
+ opcode = 'JSOP_CPP_COMMENT'
+
+ start_offset = match.start()+1
+ end_offset = match.end()
+
+ # Make sure it doesn't start in a string or regexp
+ if not ignore_ranges.has(start_offset):
+ start_pos = node_positions.from_offset(start_offset)
+ end_pos = node_positions.from_offset(end_offset)
+ kwargs = {
+ 'type': 'COMMENT',
+ 'atom': comment_text,
+ 'opcode': opcode,
+ 'start_row': start_pos.line,
+ 'start_col': start_pos.col,
+ 'end_row': end_pos.line,
+ 'end_col': end_pos.col,
+ 'kids': [],
+ 'node_index': None
+ }
+ comment_node = _Node(kwargs)
+ comments.append(comment_node)
+ pos = match.end()
+ else:
+ pos = match.start()+1
+
+def parse(script, error_callback):
+ def _wrapped_callback(line, col, msg):
+ assert msg.startswith('JSMSG_')
+ msg = msg[6:].lower()
+ error_callback(line, col, msg)
+
+ positions = NodePositions(script)
+
+ roots = []
+ nodes = []
+ comment_ignore_ranges = NodeRanges()
+ def process(node):
+ if node.kind == 'number':
+ node.atom = positions.text(node.start_pos(), node.end_pos())
+ elif node.kind == 'string' or \
+ (node.kind == 'object' and node.opcode == 'regexp'):
+ start_offset = positions.to_offset(node.start_pos())
+ end_offset = positions.to_offset(node.end_pos())
+ comment_ignore_ranges.add(start_offset, end_offset)
+ for kid in node.kids:
+ if kid:
+ process(kid)
+ def pop():
+ nodes.pop()
+
+ roots = pyspidermonkey.traverse(script, _wrapped_callback)
+ assert len(roots) == 1
+ root_node = _Node(roots[0])
+ process(root_node)
+
+ comments = _parse_comments(script, root_node, positions, comment_ignore_ranges)
+ return root_node, comments
+
+def _dump_node(node, depth=0):
+ print '. '*depth,
+ if node is None:
+ print '(none)'
+ else:
+ print node.kind, '\t', node.args
+ for node in node.kids:
+ _dump_node(node, depth+1)
+
+def dump_tree(script):
+ def error_callback(line, col, msg):
+ print '(%i, %i): %s', (line, col, msg)
+ node, comments = parse(script, error_callback)
+ _dump_node(node)
+
+class TestComments(unittest.TestCase):
+ def _test(self, script, expected_comments):
+ root, comments = parse(script, lambda line, col, msg: None)
+ encountered_comments = [node.atom for node in comments]
+ self.assertEquals(encountered_comments, list(expected_comments))
+ def testSimpleComments(self):
+ self._test('re = /\//g', ())
+ self._test('re = /\///g', ())
+ self._test('re = /\////g', ('g',))
+ def testCComments(self):
+ self._test('/*a*//*b*/', ('a', 'b'))
+ self._test('/*a\r\na*//*b\r\nb*/', ('a\r\na', 'b\r\nb'))
+ self._test('a//*b*/c', ('*b*/c',))
+ self._test('a///*b*/c', ('/*b*/c',))
+ self._test('a/*//*/;', ('//',))
+ self._test('a/*b*/+/*c*/d', ('b', 'c'))
+
+class TestNodePositions(unittest.TestCase):
+ def _test(self, text, expected_lines, expected_cols):
+ # Get a NodePos list
+ positions = NodePositions(text)
+ positions = [positions.from_offset(i) for i in range(0, len(text))]
+ encountered_lines = ''.join([str(x.line) for x in positions])
+ encountered_cols = ''.join([str(x.col) for x in positions])
+ self.assertEquals(encountered_lines, expected_lines.replace(' ', ''))
+ self.assertEquals(encountered_cols, expected_cols.replace(' ', ''))
+ def testSimple(self):
+ self._test(
+ 'abc\r\ndef\nghi\n\nj',
+ '0000 0 1111 2222 3 4',
+ '0123 4 0123 0123 0 0'
+ )
+ self._test(
+ '\rabc',
+ '0 111',
+ '0 012'
+ )
+ def testText(self):
+ pos = NodePositions('abc\r\ndef\n\nghi')
+ self.assertEquals(pos.text(NodePos(0, 0), NodePos(0, 0)), 'a')
+ self.assertEquals(pos.text(NodePos(0, 0), NodePos(0, 2)), 'abc')
+ self.assertEquals(pos.text(NodePos(0, 2), NodePos(1, 2)), 'c\r\ndef')
+ def testOffset(self):
+ pos = NodePositions('abc\r\ndef\n\nghi')
+ self.assertEquals(pos.to_offset(NodePos(0, 2)), 2)
+ self.assertEquals(pos.to_offset(NodePos(1, 0)), 5)
+ self.assertEquals(pos.to_offset(NodePos(3, 1)), 11)
+
+class TestNodeRanges(unittest.TestCase):
+ def testAdd(self):
+ r = NodeRanges()
+ r.add(5, 10)
+ self.assertEquals(r._offsets, [5,11])
+ r.add(15, 20)
+ self.assertEquals(r._offsets, [5,11,15,21])
+ r.add(21,22)
+ self.assertEquals(r._offsets, [5,11,15,23])
+ r.add(4,5)
+ self.assertEquals(r._offsets, [4,11,15,23])
+ r.add(9,11)
+ self.assertEquals(r._offsets, [4,12,15,23])
+ r.add(10,20)
+ self.assertEquals(r._offsets, [4,23])
+ r.add(4,22)
+ self.assertEquals(r._offsets, [4,23])
+ r.add(30,30)
+ self.assertEquals(r._offsets, [4,23,30,31])
+ def testHas(self):
+ r = NodeRanges()
+ r.add(5, 10)
+ r.add(15, 15)
+ assert not r.has(4)
+ assert r.has(5)
+ assert r.has(6)
+ assert r.has(9)
+ assert r.has(10)
+ assert not r.has(14)
+ assert r.has(15)
+ assert not r.has(16)
+if __name__ == '__main__':
+ unittest.main()
+
Added: trunk/pyjsl/util.py
===================================================================
--- trunk/pyjsl/util.py (rev 0)
+++ trunk/pyjsl/util.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,16 @@
+import codecs
+import os.path
+
+def readfile(path):
+ file = codecs.open(path, 'r', 'utf-8')
+ contents = file.read()
+ if contents[0] == unicode(codecs.BOM_UTF8, 'utf8'):
+ contents = contents[1:]
+ return contents
+
+def normpath(path):
+ path = os.path.abspath(path)
+ path = os.path.normcase(path)
+ path = os.path.normpath(path)
+ return path
+
Added: trunk/pyjsl/visitation.py
===================================================================
--- trunk/pyjsl/visitation.py (rev 0)
+++ trunk/pyjsl/visitation.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,35 @@
+""" This is an abstract module for visiting specific nodes. This is useed to
+traverse the tree to generate warnings.
+"""
+
+def visit(*args):
+ """ This decorator is used to indicate which nodes the function should
+ examine. The function should accept (self, node) and return the relevant
+ node or None. """
+ def _decorate(fn):
+ fn._visit_nodes = args
+ return fn
+ return _decorate
+
+def make_visitors(klasses):
+ """ Searches klasses for all member functions decorated with @visit and
+ returns a dictionary that maps from node type to visitor function. """
+ visitors = {}
+
+ # Intantiate an instance of each class
+ for klass in klasses:
+ if klass.__name__.lower() != klass.__name__:
+ raise ValueError, 'class names must be lowercase'
+ if not klass.__doc__:
+ raise ValueError, 'missing docstring on class'
+
+ # Look for functions with the "_visit_nodes" property.
+ visitor = klass()
+ for func in [getattr(visitor, name) for name in dir(visitor)]:
+ for node_kind in getattr(func, '_visit_nodes', ()):
+ # Map from node_kind to the function
+ if not node_kind in visitors:
+ visitors[node_kind] = []
+ visitors[node_kind].append(func)
+ return visitors
+
Added: trunk/pyjsl/warnings.py
===================================================================
--- trunk/pyjsl/warnings.py (rev 0)
+++ trunk/pyjsl/warnings.py 2008-03-01 17:21:01 UTC (rev 147)
@@ -0,0 +1,475 @@
+""" This module contains all the warnings. To add a new warning, define a
+class. Its name should be in lowercase and words should be separated by
+underscores. Its docstring should be the warning message.
+
+The class can have one more more member functions to inspect nodes. The
+function should be decorated with a @lookat call specifying the nodes it
+wants to examine. The node names may be in the 'kind' or 'kind:opcode'
+format. To report a warning, the function should return the node causing
+the warning.
+
+For example:
+
+ class warning_name:
+ 'questionable JavaScript coding style'
+ @lookat('nodekind', 'nodekind:opcode')
+ def _lint(self, node):
+ if questionable:
+ return node
+"""
+import re
+import sys
+import types
+
+from visitation import visit as lookat
+# TODO: document inspect, node:opcode, etc
+
+def _get_branch_in_for(node):
+ " Returns None if this is not one of the branches in a 'for' "
+ if node.parent and node.parent.kind == 'reserved' and \
+ node.parent.parent.kind == 'for':
+ return node.node_index
+ return None
+
+def _get_exit_points(node):
+ if node.kind == 'lc':
+ # Only if the last child contains it
+ exit_points = set([None])
+ for kid in node.kids:
+ # "None" is only a valid exit point for the last statement.
+ if None in exit_points:
+ exit_points.remove(None)
+ if kid:
+ exit_points |= _get_exit_points(kid)
+ elif node.kind == 'if':
+ # Only if both branches have an exit point
+ cond_, if_, else_ = node.kids
+ exit_points = _get_exit_points(if_)
+ if else_:
+ exit_points |= _get_exit_points(else_)
+ elif node.kind == 'switch':
+ exit_points = set([None])
+
+ switch_has_default = False
+ switch_has_final_fallthru = True
+
+ switch_var, switch_stmts = node.kids
+ for node in switch_stmts.kids:
+ case_val, case_stmt = node.kids
+ case_exit_points = _get_exit_points(case_stmt)
+ switch_has_default = switch_has_default or node.kind == 'default'
+ switch_has_final_fallthru = None in case_exit_points
+ exit_points |= case_exit_points
+
+ # Correct the "None" exit point.
+ exit_points.remove(None)
+
+ # Check if the switch contained any break
+ if 'break' in exit_points:
+ exit_points.remove('break')
+ exit_points.add(None)
+
+ # Check if the switch had a default case
+ if not switch_has_default:
+ exit_points.add(None)
+
+ # Check if the final case statement had a fallthru
+ if switch_has_final_fallthru:
+ exit_points.add(None)
+ elif node.kind == 'break':
+ exit_points = set(['break'])
+ elif node.kind == 'with':
+ exit_points = _get_exit_points(node.kids[-1])
+ elif node.kind == 'return':
+ exit_points = set(['return'])
+ elif node.kind == 'throw':
+ exit_points = set(['throw'])
+ elif node.kind == 'try':
+ try_, catch_, finally_ = node.kids
+
+ exit_points = _get_exit_points(try_) | _get_exit_points(catch_)
+ if finally_:
+ # Always if the finally has an exit point
+ if None in exit_points:
+ exit_points.remove(None)
+ exit_points |= _get_exit_points(finally_)
+ else:
+ exit_points = set([None])
+
+ return exit_points
+
+class comparison_type_conv:
+ 'comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)'
+ @lookat('eqop:eq')
+ def _lint(self, node):
+ lvalue, rvalue = node.kids
+ if not self._allow_coercive_compare(lvalue) or \
+ not self._allow_coercive_compare(rvalue):
+ return node
+ def _allow_coercive_compare(self, node):
+ if node.kind == 'primary' and node.opcode in ('null', 'true', 'false'):
+ return False
+ if node.kind == 'number' and not node.dval:
+ return False
+ if node.kind == 'string' and not node.atom:
+ return False
+ return True
+
+class default_not_at_end:
+ 'the default case is not at the end of the switch statement'
+ @lookat('default')
+ def _lint(self, node):
+ siblings = node.parent.kids
+ if node.node_index != len(siblings)-1:
+ return siblings[node.node_index+1]
+
+class duplicate_case_in_switch:
+ 'duplicate case in switch statement'
+ @lookat('case')
+ def _lint(self, node):
+ # Only look at previous siblings
+ siblings = node.parent.kids
+ siblings = siblings[:node.node_index]
+ # Compare values (first kid)
+ node_value = node.kids[0]
+ for sibling in siblings:
+ if sibling.kind == 'case':
+ sibling_value = sibling.kids[0]
+ if node_value.is_equivalent(sibling_value, True):
+ return node
+
+class missing_default_case:
+ 'missing default case in switch statement'
+ @lookat('switch')
+ def _lint(self, node):
+ value, cases = node.kids
+ for case in cases.kids:
+ if case.kind == 'default':
+ return
+ return node
+
+class with_statement:
+ 'with statement hides undeclared variables; use temporary variable instead'
+ @lookat('with')
+ def _lint(self, node):
+ return node
+
+class useless_comparison:
+ 'useless comparison; comparing identical expressions'
+ @lookat('eqop','relop')
+ def _lint(self, node):
+ lvalue, rvalue = node.kids
+ if lvalue.is_equivalent(rvalue):
+ return node
+
+class use_of_label:
+ 'use of label'
+ @lookat('colon:name')
+ def _lint(self, node):
+ return node
+
+class meaningless_block:
+ 'meaningless block; curly braces have no impact'
+ @lookat('lc')
+ def _lint(self, node):
+ if node.parent and node.parent.kind == 'lc':
+ return node
+
+class misplaced_regex:
+ 'regular expressions should be preceded by a left parenthesis, assignment, colon, or comma'
+ @lookat('object:regexp')
+ def _lint(self, node):
+ if node.parent.kind == 'name' and node.parent.opcode == 'setname':
+ return # Allow in var statements
+ if node.parent.kind == 'assign' and node.parent.opcode == 'nop':
+ return # Allow in assigns
+ if node.parent.kind == 'colon' and node.parent.parent.kind == 'rc':
+ return # Allow in object literals
+ if node.parent.kind == 'lp' and node.parent.opcode == 'call':
+ return # Allow in parameters
+ if node.parent.kind == 'dot' and node.parent.opcode == 'getprop':
+ return # Allow in /re/.property
+ if node.parent.kind == 'return':
+ return # Allow for return values
+ return node
+
+class assign_to_function_call:
+ 'assignment to a function call'
+ @lookat('assign')
+ def _lint(self, node):
+ if node.kids[0].kind == 'lp':
+ return node
+
+class ambiguous_else_stmt:
+ 'the else statement could be matched with one of multiple if statements (use curly braces to indicate intent'
+ @lookat('if')
+ def _lint(self, node):
+ # Only examine this node if it has an else statement.
+ condition, if_, else_ = node.kids
+ if not else_:
+ return
+
+ tmp = node
+ while tmp:
+ # Curly braces always clarify if statements.
+ if tmp.kind == 'lc':
+ return
+ # Else is only ambiguous in the first branch of an if statement.
+ if tmp.parent.kind == 'if' and tmp.node_index == 1:
+ return else_
+ tmp = tmp.parent
+
+class block_without_braces:
+ 'block statement without curly braces'
+ @lookat('if', 'while', 'do', 'for', 'with')
+ def _lint(self, node):
+ if node.kids[1].kind != 'lc':
+ return node.kids[1]
+
+class ambiguous_nested_stmt:
+ 'block statements containing block statements should use curly braces to resolve ambiguity'
+ _block_nodes = ('if', 'while', 'do', 'for', 'with')
+ @lookat(*_block_nodes)
+ def _lint(self, node):
+ # Ignore "else if"
+ if node.kind == 'if' and node.node_index == 2 and node.parent.kind == 'if':
+ return
+
+ # If the parent is a block, it means a block statement
+ # was inside a block statement without clarifying curlies.
+ # (Otherwise, the node type would be 'lc'.)
+ if node.parent.kind in self._block_nodes:
+ return node
+
+class inc_dec_within_stmt:
+ 'increment (++) and decrement (--) operators used as part of greater statement'
+ @lookat('inc', 'dec')
+ def _lint(self, node):
+ if node.parent.kind == 'semi':
+ return None
+
+ # Allow within the third part of the "for"
+ tmp = node
+ while tmp and tmp.parent and tmp.parent.kind == 'comma':
+ tmp = tmp.parent
+ if tmp and tmp.node_index == 2 and \
+ tmp.parent.kind == 'reserved' and \
+ tmp.parent.parent.kind == 'for':
+ return None
+
+ return node
+ def _is_for_ternary_stmt(self, node, branch=None):
+ if node.parent and node.parent.kind == 'comma':
+ return _is_for_ternary_stmt(node.parent, branch)
+ return node.node_index == branch and \
+ node.parent and \
+ node.parent.kind == 'reserved' and \
+ node.parent.parent.kind == 'for'
+
+class comma_separated_stmts:
+ 'multiple statements separated by commas (use semicolons?)'
+ @lookat('comma')
+ def _lint(self, node):
+ # Allow within the first and third part of "for(;;)"
+ if _get_branch_in_for(node) in (0, 2):
+ return
+ # This is an array
+ if node.parent.kind == 'rb':
+ return
+ return node
+
+class empty_statement:
+ 'empty statement or extra semicolon'
+ @lookat('semi')
+ def _semi(self, node):
+ if not node.kids[0]:
+ return node
+ @lookat('lc')
+ def _lc(self, node):
+ if node.kids:
+ return
+ # Ignore the outermost block.
+ if not node.parent:
+ return
+ # Some empty blocks are meaningful.
+ if node.parent.kind in ('catch', 'case', 'default', 'switch', 'function'):
+ return
+ return node
+
+class missing_break:
+ 'missing break statement'
+ @lookat('case', 'default')
+ def _lint(self, node):
+ # The last item is handled separately
+ if node.node_index == len(node.parent.kids)-1:
+ return
+ case_contents = node.kids[1]
+ assert case_contents.kind == 'lc'
+ # Ignore empty case statements
+ if not case_contents.kids:
+ return
+ if None in _get_exit_points(case_contents):
+ return node
+
+class missing_break_for_last_case:
+ 'missing break statement for last case in switch'
+ @lookat('case', 'default')
+ def _lint(self, node):
+ if node.node_index < len(node.parent.kids)-1:
+ return
+ case_contents = node.kids[1]
+ assert case_c...
[truncated message content] |