[javascriptlint-commit] SF.net SVN: javascriptlint:[255] trunk
Status: Beta
Brought to you by:
matthiasmiller
|
From: <mat...@us...> - 2009-10-03 16:53:15
|
Revision: 255
http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=255&view=rev
Author: matthiasmiller
Date: 2009-10-03 16:53:02 +0000 (Sat, 03 Oct 2009)
Log Message:
-----------
Move the contents of pyjsl out one level.
Modified Paths:
--------------
trunk/javascriptlint/jsl.py
trunk/test.py
Added Paths:
-----------
trunk/javascriptlint/conf.py
trunk/javascriptlint/htmlparse.py
trunk/javascriptlint/jsparse.py
trunk/javascriptlint/lint.py
trunk/javascriptlint/spidermonkey.py
trunk/javascriptlint/spidermonkey_.py
trunk/javascriptlint/util.py
trunk/javascriptlint/visitation.py
trunk/javascriptlint/warnings.py
Removed Paths:
-------------
trunk/javascriptlint/pyjsl/
Copied: trunk/javascriptlint/conf.py (from rev 254, trunk/javascriptlint/pyjsl/conf.py)
===================================================================
--- trunk/javascriptlint/conf.py (rev 0)
+++ trunk/javascriptlint/conf.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,136 @@
+# vim: ts=4 sw=4 expandtab
+import os
+
+import warnings
+
+class ConfError(Exception):
+ def __init__(self, error):
+ Exception.__init__(self, 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('__FILE__(__LINE__): __ERROR__'),
+ '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 name in warnings.warnings:
+ self._settings[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
+ if '#' in line:
+ line = line[:line.find('#')]
+ 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
+
Property changes on: trunk/javascriptlint/conf.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/htmlparse.py (from rev 254, trunk/javascriptlint/pyjsl/htmlparse.py)
===================================================================
--- trunk/javascriptlint/htmlparse.py (rev 0)
+++ trunk/javascriptlint/htmlparse.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,89 @@
+# vim: ts=4 sw=4 expandtab
+import HTMLParser
+import unittest
+
+import jsparse
+
+class _Parser(HTMLParser.HTMLParser):
+ def __init__(self):
+ HTMLParser.HTMLParser.__init__(self)
+ self._node_positions = jsparse.NodePositions('')
+ self._script = None
+ self._scripts = []
+
+ def handle_starttag(self, tag, attributes):
+ if tag.lower() == 'script' and not self._script:
+ offset = self._getoffset() + len(self.get_starttag_text())
+ self._script = self._script or {
+ 'start': offset,
+ 'attributes': attributes
+ }
+
+ def handle_endtag(self, tag):
+ if tag.lower() == 'script' and self._script:
+ start = self._script['start']
+ end = self._getoffset()
+ script = self.rawdata[start:end]
+ if jsparse.is_compilable_unit(script):
+ attr = dict(self._script['attributes'])
+ self._scripts.append({
+ 'script': script,
+ 'startoffset': start,
+ 'endoffset': end,
+ 'startpos': self._node_positions.from_offset(start),
+ 'endpos': self._node_positions.from_offset(end),
+ 'src': attr.get('src'),
+ 'type': attr.get('type')
+ })
+ self._script = None
+
+ def feed(self, data):
+ self._node_positions = jsparse.NodePositions(self.rawdata + data)
+ HTMLParser.HTMLParser.feed(self, data)
+
+ def unknown_decl(self, data):
+ # Ignore unknown declarations instead of raising an exception.
+ pass
+
+ def getscripts(self):
+ return self._scripts
+
+ def _getnodepos(self):
+ line, col = self.getpos()
+ return jsparse.NodePos(line - 1, col)
+
+ def _getoffset(self):
+ return self._node_positions.to_offset(self._getnodepos())
+
+def findscripts(s):
+ parser = _Parser()
+ parser.feed(s)
+ parser.close()
+ return parser.getscripts()
+
+class TestHTMLParse(unittest.TestCase):
+ def testFindScript(self):
+ html = """
+<html><body>
+<script src=test.js></script>
+hi&b
+a<script><!--
+var s = '<script></script>';
+--></script>
+ok&
+..</script>
+ok&
+</body>
+</html>
+"""
+ scripts = [x['script'] for x in findscripts(html)]
+ self.assertEquals(scripts, [
+ "",
+ "<!--\nvar s = '<script></script>';\n-->"
+ ])
+ def testConditionalComments(self):
+ html = """
+<!--[if IE]>This is Internet Explorer.<![endif]-->
+<![if !IE]>This is not Internet Explorer<![endif]>
+"""
+ findscripts(html)
Property changes on: trunk/javascriptlint/htmlparse.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Modified: trunk/javascriptlint/jsl.py
===================================================================
--- trunk/javascriptlint/jsl.py 2009-10-03 16:41:05 UTC (rev 254)
+++ trunk/javascriptlint/jsl.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -7,11 +7,11 @@
import unittest
from optparse import OptionParser
-import pyjsl.conf
-import pyjsl.htmlparse
-import pyjsl.jsparse
-import pyjsl.lint
-import pyjsl.util
+import conf
+import htmlparse
+import jsparse
+import lint
+import util
_lint_results = {
'warnings': 0,
@@ -20,15 +20,15 @@
def _dump(paths):
for path in paths:
- script = pyjsl.util.readfile(path)
- pyjsl.jsparse.dump_tree(script)
+ script = util.readfile(path)
+ jsparse.dump_tree(script)
-def _lint(paths, conf):
+def _lint(paths, conf_):
def lint_error(path, line, col, errname, errdesc):
_lint_results['warnings'] = _lint_results['warnings'] + 1
- print pyjsl.util.format_error(conf['output-format'], path, line, col,
+ print util.format_error(conf_['output-format'], path, line, col,
errname, errdesc)
- pyjsl.lint.lint_files(paths, lint_error, conf=conf)
+ lint.lint_files(paths, lint_error, conf=conf_)
def _resolve_paths(path, recurse):
if os.path.isfile(path):
@@ -88,9 +88,9 @@
parser.print_help()
sys.exit()
- conf = pyjsl.conf.Conf()
+ conf_ = conf.Conf()
if options.conf:
- conf.loadfile(options.conf)
+ conf_.loadfile(options.conf)
profile_func = _profile_disabled
if options.profile:
@@ -98,21 +98,21 @@
if options.unittest:
suite = unittest.TestSuite();
- for module in [pyjsl.htmlparse, pyjsl.jsparse, pyjsl.util]:
+ for module in [htmlparse, jsparse, util]:
suite.addTest(unittest.findTestCases(module))
runner = unittest.TextTestRunner(verbosity=options.verbosity)
runner.run(suite)
paths = []
- for recurse, path in conf['paths']:
+ for recurse, path in conf_['paths']:
paths.extend(_resolve_paths(path, recurse))
for arg in args:
paths.extend(_resolve_paths(arg, False))
if options.dump:
profile_func(_dump, paths)
else:
- profile_func(_lint, paths, conf)
+ profile_func(_lint, paths, conf_)
if _lint_results['errors']:
sys.exit(3)
Copied: trunk/javascriptlint/jsparse.py (from rev 254, trunk/javascriptlint/pyjsl/jsparse.py)
===================================================================
--- trunk/javascriptlint/jsparse.py (rev 0)
+++ trunk/javascriptlint/jsparse.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,414 @@
+#!/usr/bin/python
+# vim: ts=4 sw=4 expandtab
+""" Parses a script into nodes. """
+import bisect
+import re
+import unittest
+
+import spidermonkey
+from spidermonkey import tok, op
+
+_tok_names = dict(zip(
+ [getattr(tok, prop) for prop in dir(tok)],
+ ['tok.%s' % prop for prop in dir(tok)]
+))
+_op_names = dict(zip(
+ [getattr(op, prop) for prop in dir(op)],
+ ['op.%s' % prop for prop in dir(op)]
+))
+
+NodePos = spidermonkey.NodePos
+
+class NodePositions:
+ " Given a string, allows [x] lookups for NodePos line and column numbers."
+ def __init__(self, text, start_pos=None):
+ # Find the length of each line and incrementally sum all of the lengths
+ # to determine the ending position of each line.
+ self._start_pos = start_pos
+ 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]
+ if self._start_pos:
+ if line == 0:
+ col += self._start_pos.col
+ line += self._start_pos.line
+ return NodePos(line, col)
+ def to_offset(self, pos):
+ pos = self._to_rel_pos(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
+ start, end = self._to_rel_pos(start), self._to_rel_pos(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)
+ def _to_rel_pos(self, pos):
+ " converts a position to a position relative to self._start_pos "
+ if not self._start_pos:
+ return pos
+ line, col = pos.line, pos.col
+ line -= self._start_pos.line
+ if line == 0:
+ col -= self._start_pos.col
+ assert line >= 0 and col >= 0 # out-of-bounds node position
+ return NodePos(line, col)
+
+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 add_child(self, node):
+ if node:
+ node.node_index = len(self.kids)
+ node.parent = self
+ self.kids.append(node)
+
+ def start_pos(self):
+ try:
+ return self._start_pos
+ except AttributeError:
+ self._start_pos = NodePos(self._start_line, self._start_col)
+ return self._start_pos
+
+ def end_pos(self):
+ try:
+ return self._end_pos
+ except AttributeError:
+ self._end_pos = NodePos(self._end_line, self._end_col)
+ return self._end_pos
+
+ def __str__(self):
+ kind = self.kind
+ if not kind:
+ kind = '(none)'
+ return '%s>%s' % (_tok_names[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 == tok.FUNCTION:
+ return False
+ if self.kind == tok.LP and self.opcode == op.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 (tok.NAME, tok.DOT, tok.STRING) and self.atom != other.atom:
+ return False
+
+ # Check values on numbers
+ if self.kind == tok.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 findpossiblecomments(script, node_positions):
+ 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'
+ opcode = opcode[5:].lower()
+
+ start_offset = match.start()
+ end_offset = match.end()-1
+
+ 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_line': start_pos.line,
+ '_start_col': start_pos.col,
+ '_end_line': end_pos.line,
+ '_end_col': end_pos.col,
+ 'parent': None,
+ 'kids': [],
+ 'node_index': None
+ }
+ comment_node = _Node()
+ comment_node.__dict__.update(kwargs)
+ comments.append(comment_node)
+
+ # Start searching immediately after the start of the comment in case
+ # this one was within a string or a regexp.
+ pos = match.start()+1
+
+def parse(script, error_callback, startpos=None):
+ """ All node positions will be relative to startpos. This allows scripts
+ to be embedded in a file (for example, HTML).
+ """
+ def _wrapped_callback(line, col, msg):
+ assert msg.startswith('JSMSG_')
+ msg = msg[6:].lower()
+ error_callback(line, col, msg)
+
+ startpos = startpos or NodePos(0,0)
+ return spidermonkey.parse(script, _Node, _wrapped_callback,
+ startpos.line, startpos.col)
+
+def filtercomments(possible_comments, node_positions, root_node):
+ comment_ignore_ranges = NodeRanges()
+
+ def process(node):
+ if node.kind == tok.NUMBER:
+ node.atom = node_positions.text(node.start_pos(), node.end_pos())
+ elif node.kind == tok.STRING or \
+ (node.kind == tok.OBJECT and node.opcode == op.REGEXP):
+ start_offset = node_positions.to_offset(node.start_pos())
+ end_offset = node_positions.to_offset(node.end_pos()) - 1
+ comment_ignore_ranges.add(start_offset, end_offset)
+ for kid in node.kids:
+ if kid:
+ process(kid)
+ process(root_node)
+
+ comments = []
+ for comment in possible_comments:
+ start_offset = node_positions.to_offset(comment.start_pos())
+ end_offset = node_positions.to_offset(comment.end_pos())
+ if comment_ignore_ranges.has(start_offset):
+ continue
+ comment_ignore_ranges.add(start_offset, end_offset)
+ comments.append(comment)
+ return comments
+
+def findcomments(script, root_node, start_pos=None):
+ node_positions = NodePositions(script, start_pos)
+ possible_comments = findpossiblecomments(script, node_positions)
+ return filtercomments(possible_comments, node_positions, root_node)
+
+def is_compilable_unit(script):
+ return spidermonkey.is_compilable_unit(script)
+
+def _dump_node(node, depth=0):
+ if node is None:
+ print ' '*depth,
+ print '(None)'
+ print
+ else:
+ print ' '*depth,
+ print '%s, %s' % (_tok_names[node.kind], _op_names[node.opcode])
+ print ' '*depth,
+ print '%s - %s' % (node.start_pos(), node.end_pos())
+ if hasattr(node, 'atom'):
+ print ' '*depth,
+ print 'atom: %s' % node.atom
+ if node.no_semi:
+ print ' '*depth,
+ print '(no semicolon)'
+ print
+ 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 = parse(script, error_callback)
+ _dump_node(node)
+
+class TestComments(unittest.TestCase):
+ def _test(self, script, expected_comments):
+ root = parse(script, lambda line, col, msg: None)
+ comments = findcomments(script, root)
+ 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)
+ def testStartPos(self):
+ pos = NodePositions('abc\r\ndef\n\nghi', NodePos(3,4))
+ self.assertEquals(pos.to_offset(NodePos(3, 4)), 0)
+ self.assertEquals(pos.to_offset(NodePos(3, 5)), 1)
+ self.assertEquals(pos.from_offset(0), NodePos(3, 4))
+ self.assertEquals(pos.text(NodePos(3, 4), NodePos(3, 4)), 'a')
+ self.assertEquals(pos.text(NodePos(3, 4), NodePos(3, 6)), 'abc')
+ self.assertEquals(pos.text(NodePos(3, 6), NodePos(4, 2)), 'c\r\ndef')
+
+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)
+
+class TestCompilableUnit(unittest.TestCase):
+ def test(self):
+ tests = (
+ ('var s = "', False),
+ ('bogon()', True),
+ ('int syntax_error;', True),
+ ('a /* b', False),
+ ('re = /.*', False),
+ ('{ // missing curly', False)
+ )
+ for text, result in tests:
+ self.assertEquals(is_compilable_unit(text), result)
+ # NOTE: This seems like a bug.
+ self.assert_(is_compilable_unit("/* test"))
+
+class TestLineOffset(unittest.TestCase):
+ def testErrorPos(self):
+ def geterror(script, startpos):
+ errors = []
+ def onerror(line, col, msg):
+ errors.append((line, col, msg))
+ parse(script, onerror, startpos)
+ self.assertEquals(len(errors), 1)
+ return errors[0]
+ self.assertEquals(geterror(' ?', None), (0, 1, 'syntax_error'))
+ self.assertEquals(geterror('\n ?', None), (1, 1, 'syntax_error'))
+ self.assertEquals(geterror(' ?', NodePos(1,1)), (1, 2, 'syntax_error'))
+ self.assertEquals(geterror('\n ?', NodePos(1,1)), (2, 1, 'syntax_error'))
+ def testNodePos(self):
+ def getnodepos(script, startpos):
+ root = parse(script, None, startpos)
+ self.assertEquals(root.kind, tok.LC)
+ var, = root.kids
+ self.assertEquals(var.kind, tok.VAR)
+ return var.start_pos()
+ self.assertEquals(getnodepos('var x;', None), NodePos(0,0))
+ self.assertEquals(getnodepos(' var x;', None), NodePos(0,1))
+ self.assertEquals(getnodepos('\n\n var x;', None), NodePos(2,1))
+ self.assertEquals(getnodepos('var x;', NodePos(3,4)), NodePos(3,4))
+ self.assertEquals(getnodepos(' var x;', NodePos(3,4)), NodePos(3,5))
+ self.assertEquals(getnodepos('\n\n var x;', NodePos(3,4)), NodePos(5,1))
+ def testComments(self):
+ def testcomment(comment, startpos, expectedpos):
+ root = parse(comment, None, startpos)
+ comment, = findcomments(comment, root, startpos)
+ self.assertEquals(comment.start_pos(), expectedpos)
+ for comment in ('/*comment*/', '//comment'):
+ testcomment(comment, None, NodePos(0,0))
+ testcomment(' %s' % comment, None, NodePos(0,1))
+ testcomment('\n\n %s' % comment, None, NodePos(2,1))
+ testcomment('%s' % comment, NodePos(3,4), NodePos(3,4))
+ testcomment(' %s' % comment, NodePos(3,4), NodePos(3,5))
+ testcomment('\n\n %s' % comment, NodePos(3,4), NodePos(5,1))
+
+if __name__ == '__main__':
+ unittest.main()
+
Property changes on: trunk/javascriptlint/jsparse.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/lint.py (from rev 254, trunk/javascriptlint/pyjsl/lint.py)
===================================================================
--- trunk/javascriptlint/lint.py (rev 0)
+++ trunk/javascriptlint/lint.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,505 @@
+#!/usr/bin/python
+# vim: ts=4 sw=4 expandtab
+import os.path
+import re
+
+import conf
+import htmlparse
+import jsparse
+import visitation
+import warnings
+import util
+
+from spidermonkey import tok, op
+
+_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'
+])
+
+def _find_function(node):
+ while node and node.kind != tok.FUNCTION:
+ node = node.parent
+ return node
+
+def _find_functions(node):
+ functions = []
+ while node:
+ if node.kind == tok.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]
+ if not keyword in control_comments:
+ return None
+
+ parms = control_comment[len(keyword):].strip()
+ return (comment, keyword, parms)
+
+class Scope:
+ """ Outer-level scopes will never be associated with a node.
+ Inner-level scopes will always be associated with a node.
+ """
+ def __init__(self):
+ self._parent = None
+ self._kids = []
+ self._identifiers = {}
+ self._references = []
+ self._node = None
+ def add_scope(self, node):
+ assert not node is None
+ self._kids.append(Scope())
+ self._kids[-1]._parent = self
+ self._kids[-1]._node = node
+ return self._kids[-1]
+ def add_declaration(self, name, node):
+ self._identifiers[name] = node
+ def add_reference(self, name, node):
+ 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_unreferenced_and_undeclared_identifiers(self):
+ """ Returns a tuple of unreferenced and undeclared, where each is a list
+ of (scope, name, node) tuples.
+ """
+ unreferenced = {}
+ undeclared = []
+ self._find_unreferenced_and_undeclared(unreferenced, undeclared, False)
+
+ # Convert "unreferenced" from a dictionary of:
+ # { (scope, name): node }
+ # to a list of:
+ # [ (scope, name, node) ]
+ # sorted by node position.
+ unreferenced = [(key[0], key[1], node) for key, node
+ in unreferenced.items()]
+ unreferenced.sort(key=lambda x: x[2].start_pos())
+
+ return unreferenced, undeclared
+ def _find_unreferenced_and_undeclared(self, unreferenced, undeclared,
+ is_in_with_scope):
+ """ unreferenced is a dictionary, such that:
+ (scope, name): node
+ }
+ undeclared is a list, such that: [
+ (scope, name, node)
+ ]
+ """
+ if self._node and self._node.kind == tok.WITH:
+ is_in_with_scope = True
+
+ # Add all identifiers as unreferenced. Children scopes will remove
+ # them if they are referenced. Variables need to be keyed by name
+ # instead of node, because function parameters share the same node.
+ for name, node in self._identifiers.items():
+ unreferenced[(self, name)] = node
+
+ # Remove all declared variables from the "unreferenced" set; add all
+ # undeclared variables to the "undeclared" list.
+ for name, node in self._references:
+ resolved = self.resolve_identifier(name)
+ if resolved:
+ # Make sure this isn't an assignment.
+ if node.parent.kind in (tok.ASSIGN, tok.INC, tok.DEC) and \
+ node.node_index == 0 and \
+ node.parent.parent.kind == tok.SEMI:
+ continue
+ unreferenced.pop((resolved[0], name), None)
+ else:
+ # with statements cannot have undeclared identifiers.
+ if not is_in_with_scope:
+ undeclared.append((self, name, node))
+
+ for child in self._kids:
+ child._find_unreferenced_and_undeclared(unreferenced, undeclared,
+ is_in_with_scope)
+ 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:
+ assert not self._node
+ return self
+
+ # Conditionally add it to an inner scope.
+ assert self._node
+ if (node.start_pos() >= self._node.start_pos() and \
+ node.end_pos() <= self._node.end_pos()):
+ return self
+
+class _Script:
+ def __init__(self):
+ self._imports = set()
+ self.scope = Scope()
+ def importscript(self, script):
+ self._imports.add(script)
+ def hasglobal(self, name):
+ return not self._findglobal(name, set()) is None
+ def _findglobal(self, name, searched):
+ """ searched is a set of all searched scripts """
+ # Avoid recursion.
+ if self in searched:
+ return
+
+ # Check this scope.
+ if self.scope.get_identifier(name):
+ return self
+ searched.add(self)
+
+ # Search imported scopes.
+ for script in self._imports:
+ global_ = script._findglobal(name, searched)
+ if global_:
+ return global_
+
+def lint_files(paths, lint_error, conf=conf.Conf()):
+ def lint_file(path, kind):
+ def import_script(import_path):
+ # The user can specify paths using backslashes (such as when
+ # linting Windows scripts on a posix environment.
+ import_path = import_path.replace('\\', os.sep)
+ import_path = os.path.join(os.path.dirname(path), import_path)
+ return lint_file(import_path, 'js')
+ def _lint_error(*args):
+ return lint_error(normpath, *args)
+
+ normpath = util.normpath(path)
+ if normpath in lint_cache:
+ return lint_cache[normpath]
+ print normpath
+ contents = util.readfile(path)
+ lint_cache[normpath] = _Script()
+
+ script_parts = []
+ if kind == 'js':
+ script_parts.append((None, contents))
+ elif kind == 'html':
+ for script in htmlparse.findscripts(contents):
+ if script['src']:
+ other = import_script(script['src'])
+ lint_cache[normpath].importscript(other)
+ if script['script'].strip():
+ script_parts.append((script['startpos'], script['script']))
+ else:
+ assert False, 'Unsupported file kind: %s' % kind
+
+ _lint_script_parts(script_parts, lint_cache[normpath], _lint_error, conf, import_script)
+ return lint_cache[normpath]
+
+ lint_cache = {}
+ for path in paths:
+ ext = os.path.splitext(path)[1]
+ if ext.lower() in ['.htm', '.html']:
+ lint_file(path, 'html')
+ else:
+ lint_file(path, 'js')
+
+def _lint_script_part(scriptpos, script, script_cache, conf, ignores,
+ report_native, report_lint, import_callback):
+ def parse_error(row, col, msg):
+ if not msg in ('anon_no_return_value', 'no_return_value',
+ 'redeclared_var', 'var_hides_arg'):
+ parse_errors.append((jsparse.NodePos(row, col), msg))
+
+ def report(node, errname, pos=None, **errargs):
+ if errname == 'empty_statement' and node.kind == tok.LC:
+ for pass_ in passes:
+ if pass_.start_pos() > node.start_pos() and \
+ pass_.end_pos() < node.end_pos():
+ passes.remove(pass_)
+ return
+
+ if errname == 'missing_break':
+ # Find the end of the previous case/default and the beginning of
+ # the next case/default.
+ assert node.kind in (tok.CASE, tok.DEFAULT)
+ prevnode = node.parent.kids[node.node_index-1]
+ expectedfallthru = prevnode.end_pos(), node.start_pos()
+ elif errname == 'missing_break_for_last_case':
+ # Find the end of the current case/default and the end of the
+ # switch.
+ assert node.parent.kind == tok.LC
+ expectedfallthru = node.end_pos(), node.parent.end_pos()
+ else:
+ expectedfallthru = None
+
+ if expectedfallthru:
+ start, end = expectedfallthru
+ for fallthru in fallthrus:
+ # Look for a fallthru between the end of the current case or
+ # default statement and the beginning of the next token.
+ if fallthru.start_pos() > start and fallthru.end_pos() < end:
+ fallthrus.remove(fallthru)
+ return
+
+ report_lint(node, errname, pos, **errargs)
+
+ parse_errors = []
+ declares = []
+ import_paths = []
+ fallthrus = []
+ passes = []
+
+ node_positions = jsparse.NodePositions(script, scriptpos)
+ possible_comments = jsparse.findpossiblecomments(script, node_positions)
+
+ root = jsparse.parse(script, parse_error, scriptpos)
+ if not root:
+ # Report errors and quit.
+ for pos, msg in parse_errors:
+ report_native(pos, msg)
+ return
+
+ comments = jsparse.filtercomments(possible_comments, node_positions, root)
+ start_ignore = None
+ for comment in comments:
+ cc = _parse_control_comment(comment)
+ if cc:
+ node, keyword, parms = cc
+ if keyword == 'declare':
+ if not util.isidentifier(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)
+ elif keyword == 'fallthru':
+ fallthrus.append(node)
+ elif keyword == 'pass':
+ passes.append(node)
+ else:
+ if comment.opcode == 'c_comment':
+ # Look for nested C-style comments.
+ nested_comment = comment.atom.find('/*')
+ if nested_comment < 0 and comment.atom.endswith('/'):
+ nested_comment = len(comment.atom) - 1
+ # Report at the actual error of the location. Add two
+ # characters for the opening two characters.
+ if nested_comment >= 0:
+ pos = node_positions.from_offset(node_positions.to_offset(comment.start_pos()) + 2 + nested_comment)
+ report(comment, 'nested_comment', pos=pos)
+ 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_native(pos, msg)
+
+ # Find all visitors and convert them into "onpush" callbacks that call "report"
+ visitors = {
+ 'push': warnings.make_visitors()
+ }
+ for event in visitors:
+ for kind, callbacks in visitors[event].items():
+ visitors[event][kind] = [_getreporter(callback, report) for callback in callbacks]
+
+ # Push the scope/variable checks.
+ visitation.make_visitors(visitors, [_get_scope_checks(script_cache.scope, report)])
+
+ # kickoff!
+ _lint_node(root, visitors)
+
+ for fallthru in fallthrus:
+ report(fallthru, 'invalid_fallthru')
+ for fallthru in passes:
+ report(fallthru, 'invalid_pass')
+
+ # Process imports by copying global declarations into the universal scope.
+ for path in import_paths:
+ script_cache.importscript(import_callback(path))
+
+ for name, node in declares:
+ declare_scope = script_cache.scope.find_scope(node)
+ if declare_scope.get_identifier(name):
+ report(node, 'redeclared_var', name=name)
+ else:
+ declare_scope.add_declaration(name, node)
+
+def _lint_script_parts(script_parts, script_cache, lint_error, conf, import_callback):
+ def report_lint(node, errname, pos=None, **errargs):
+ errdesc = warnings.format_error(errname, **errargs)
+ _report(pos or node.start_pos(), errname, errdesc, True)
+
+ def report_native(pos, errname):
+ # TODO: Format the error.
+ _report(pos, errname, errname, False)
+
+ def _report(pos, errname, errdesc, 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, errdesc)
+
+ for scriptpos, script in script_parts:
+ ignores = []
+ _lint_script_part(scriptpos, script, script_cache, conf, ignores,
+ report_native, report_lint, import_callback)
+
+ scope = script_cache.scope
+ unreferenced, undeclared = scope.get_unreferenced_and_undeclared_identifiers()
+ for decl_scope, name, node in undeclared:
+ if name in conf['declarations']:
+ continue
+ if name in _globals:
+ continue
+ if not script_cache.hasglobal(name):
+ report_lint(node, 'undeclared_identifier', name=name)
+ for ref_scope, name, node in unreferenced:
+ # Ignore the outer scope.
+ if ref_scope != scope:
+ report_lint(node, 'unreferenced_identifier', name=name)
+
+def _getreporter(visitor, report):
+ def onpush(node):
+ try:
+ ret = visitor(node)
+ assert ret is None, 'visitor should raise an exception, not return a value'
+ except warnings.LintWarning, warning:
+ # TODO: This is ugly hardcoding to improve the error positioning of
+ # "missing_semicolon" errors.
+ if visitor.warning in ('missing_semicolon', 'missing_semicolon_for_lambda'):
+ pos = warning.node.end_pos()
+ else:
+ pos = None
+ report(warning.node, visitor.warning, pos=pos, **warning.errargs)
+ return onpush
+
+def _warn_or_declare(scope, name, node, report):
+ parent_scope, other = scope.resolve_identifier(name) or (None, None)
+ if other and other.kind == tok.FUNCTION and name in other.fn_args:
+ report(node, 'var_hides_arg', name=name)
+ elif other and parent_scope == scope:
+ report(node, 'redeclared_var', name=name)
+ else:
+ # TODO: Warn when hiding a variable in a parent scope.
+ scope.add_declaration(name, node)
+
+def _get_scope_checks(scope, report):
+ scopes = [scope]
+
+ class scope_checks:
+ ' '
+ @visitation.visit('push', tok.NAME)
+ def _name(self, node):
+ if node.node_index == 0 and node.parent.kind == tok.COLON and node.parent.parent.kind == tok.RC:
+ return # left side of object literal
+ if node.parent.kind == tok.VAR:
+ _warn_or_declare(scopes[-1], node.atom, node, report)
+ return
+ if node.parent.kind == tok.CATCH:
+ scopes[-1].add_declaration(node.atom, node)
+ scopes[-1].add_reference(node.atom, node)
+
+ @visitation.visit('push', tok.FUNCTION)
+ def _push_func(self, node):
+ if node.fn_name:
+ _warn_or_declare(scopes[-1], node.fn_name, node, report)
+ self._push_scope(node)
+ for var_name in node.fn_args:
+ scopes[-1].add_declaration(var_name, node)
+
+ @visitation.visit('push', tok.LEXICALSCOPE, tok.WITH)
+ def _push_scope(self, node):
+ scopes.append(scopes[-1].add_scope(node))
+
+ @visitation.visit('pop', tok.FUNCTION, tok.LEXICALSCOPE, tok.WITH)
+ def _pop_scope(self, node):
+ scopes.pop()
+
+ return scope_checks
+
+
+def _lint_node(node, visitors):
+
+ for kind in (node.kind, (node.kind, node.opcode)):
+ if kind in visitors['push']:
+ for visitor in visitors['push'][kind]:
+ visitor(node)
+
+ for child in node.kids:
+ if child:
+ _lint_node(child, visitors)
+
+ for kind in (node.kind, (node.kind, node.opcode)):
+ if kind in visitors['pop']:
+ for visitor in visitors['pop'][kind]:
+ visitor(node)
+
+
Property changes on: trunk/javascriptlint/lint.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/spidermonkey.py (from rev 254, trunk/javascriptlint/pyjsl/spidermonkey.py)
===================================================================
--- trunk/javascriptlint/spidermonkey.py (rev 0)
+++ trunk/javascriptlint/spidermonkey.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,10 @@
+# vim: ts=4 sw=4 expandtab
+
+# This is a wrapper script to make it easier for development. It tries to
+# import the development version first, and if that fails, it goes after the
+# real version.
+try:
+ from spidermonkey_ import *
+except ImportError:
+ from pyspidermonkey import *
+
Property changes on: trunk/javascriptlint/spidermonkey.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/spidermonkey_.py (from rev 254, trunk/javascriptlint/pyjsl/spidermonkey_.py)
===================================================================
--- trunk/javascriptlint/spidermonkey_.py (rev 0)
+++ trunk/javascriptlint/spidermonkey_.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,20 @@
+# vim: ts=4 sw=4 expandtab
+from distutils.core import setup, Extension
+import os
+import sys
+
+# Add the bin directory to the module search path
+def _get_lib_path():
+ import distutils.dist
+ import distutils.command.build
+ dist = distutils.dist.Distribution()
+ build = distutils.command.build.build(dist)
+ build.finalize_options()
+ return os.path.join(os.path.dirname(__file__), '..', build.build_platlib, 'javascriptlint')
+
+sys.path.insert(0, _get_lib_path())
+try:
+ from pyspidermonkey import *
+finally:
+ sys.path.pop(0)
+
Property changes on: trunk/javascriptlint/spidermonkey_.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/util.py (from rev 254, trunk/javascriptlint/pyjsl/util.py)
===================================================================
--- trunk/javascriptlint/util.py (rev 0)
+++ trunk/javascriptlint/util.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,97 @@
+# vim: ts=4 sw=4 expandtab
+import codecs
+import os.path
+import re
+import unittest
+
+_identifier = re.compile('^[A-Za-z_$][A-Za-z0-9_$]*$')
+
+def isidentifier(text):
+ return _identifier.match(text)
+
+def _encode_error_keyword(s):
+ s = s.replace('\\', '\\\\')
+ s = s.replace('"', '\\"')
+ s = s.replace("'", "\\'")
+ s = s.replace("\t", "\\t")
+ s = s.replace("\r", "\\r")
+ s = s.replace("\n", "\\n")
+ return s
+
+def format_error(output_format, path, line, col, errname, errdesc):
+ errprefix = 'warning' #TODO
+ replacements = {
+ '__FILE__': path,
+ '__FILENAME__': os.path.basename(path),
+ '__LINE__': str(line+1),
+ '__COL__': str(col),
+ '__ERROR__': '%s: %s' % (errprefix, errdesc),
+ '__ERROR_NAME__': errname,
+ '__ERROR_PREFIX__': errprefix,
+ '__ERROR_MSG__': errdesc,
+ '__ERROR_MSGENC__': errdesc,
+ }
+
+ formatted_error = output_format
+
+ # If the output format starts with encode:, all of the keywords should be
+ # encoded.
+ if formatted_error.startswith('encode:'):
+ formatted_error = formatted_error[len('encode:'):]
+ encoded_keywords = replacements.keys()
+ else:
+ encoded_keywords = ['__ERROR_MSGENC__']
+
+ for keyword in encoded_keywords:
+ replacements[keyword] = _encode_error_keyword(replacements[keyword])
+
+ regexp = '|'.join(replacements.keys())
+ return re.sub(regexp, lambda match: replacements[match.group(0)],
+ formatted_error)
+
+def readfile(path):
+ file = codecs.open(path, 'r', 'utf-8')
+ contents = file.read()
+ if contents and 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
+
+class TestUtil(unittest.TestCase):
+ def testIdentifier(self):
+ assert not isidentifier('')
+ assert not isidentifier('0a')
+ assert not isidentifier('a b')
+ assert isidentifier('a')
+ assert isidentifier('$0')
+
+ def testEncodeKeyword(self):
+ self.assertEquals(_encode_error_keyword(r'normal text'), 'normal text')
+ self.assertEquals(_encode_error_keyword(r'a\b'), r'a\\b')
+ self.assertEquals(_encode_error_keyword(r"identifier's"), r"identifier\'s")
+ self.assertEquals(_encode_error_keyword(r'"i"'), r'\"i\"')
+ self.assertEquals(_encode_error_keyword('a\tb'), r'a\tb')
+ self.assertEquals(_encode_error_keyword('a\rb'), r'a\rb')
+ self.assertEquals(_encode_error_keyword('a\nb'), r'a\nb')
+
+ def testFormattedError(self):
+ self.assertEquals(format_error('__FILE__', '__LINE__', 1, 2, 'name', 'desc'),
+ '__LINE__')
+ self.assertEquals(format_error('__FILE__', r'c:\my\file', 1, 2, 'name', 'desc'),
+ r'c:\my\file')
+ self.assertEquals(format_error('encode:__FILE__', r'c:\my\file', 1, 2, 'name', 'desc'),
+ r'c:\\my\\file')
+ self.assertEquals(format_error('__ERROR_MSGENC__', r'c:\my\file', 1, 2, 'name', r'a\b'),
+ r'a\\b')
+ self.assertEquals(format_error('encode:__ERROR_MSGENC__', r'c:\my\file', 1, 2, 'name', r'a\b'),
+ r'a\\b')
+
+
+if __name__ == '__main__':
+ unittest.main()
+
Property changes on: trunk/javascriptlint/util.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/visitation.py (from rev 254, trunk/javascriptlint/pyjsl/visitation.py)
===================================================================
--- trunk/javascriptlint/visitation.py (rev 0)
+++ trunk/javascriptlint/visitation.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,52 @@
+# vim: ts=4 sw=4 expandtab
+""" This is an abstract module for visiting specific nodes. This is useed to
+traverse the tree to generate warnings.
+"""
+
+def visit(event, *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_event = event
+ fn._visit_nodes = args
+ return fn
+ return _decorate
+
+def make_visitors(visitors, klasses):
+ """ Searches klasses for all member functions decorated with @visit and
+ fills a dictionary that looks like:
+ visitors = {
+ 'event_name': {
+ 'node_type' : [func1, func2]
+ }
+ }
+ """
+ assert isinstance(visitors, dict)
+
+ # 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)]:
+ event_visitors = None
+ for node_kind in getattr(func, '_visit_nodes', ()):
+ # Group visitors by event (e.g. push vs pop)
+ if not event_visitors:
+ try:
+ event_visitors = visitors[func._visit_event]
+ except KeyError:
+ event_visitors = visitors[func._visit_event] = {}
+
+ # Map from node_kind to the function
+ try:
+ event_visitors[node_kind].append(func)
+ except KeyError:
+ event_visitors[node_kind] = [func]
+ return visitors
+
Property changes on: trunk/javascriptlint/visitation.py
___________________________________________________________________
Added: svn:mergeinfo
+
Added: svn:eol-style
+ native
Copied: trunk/javascriptlint/warnings.py (from rev 254, trunk/javascriptlint/pyjsl/warnings.py)
===================================================================
--- trunk/javascriptlint/warnings.py (rev 0)
+++ trunk/javascriptlint/warnings.py 2009-10-03 16:53:02 UTC (rev 255)
@@ -0,0 +1,611 @@
+# vim: ts=4 sw=4 expandtab
+""" This module contains all the warnings. To add a new warning, define a
+function. Its name should be in lowercase and words should be separated by
+underscores.
+
+The function should be decorated with a @lookfor call specifying the nodes it
+wants to examine. The node names may be in the tok.KIND or (tok.KIND, op.OPCODE)
+format. To report a warning, the function should raise a LintWarning exception.
+
+For example:
+
+ @lookfor(tok.NODEKIND, (tok.NODEKIND, op.OPCODE))
+ def warning_name(node):
+ if questionable:
+ raise LintWarning, node
+"""
+import re
+import sys
+import types
+
+import util
+import visitation
+
+from spidermonkey import tok, op
+
+_ALL_TOKENS = tuple(filter(lambda x: x != tok.EOF, tok.__dict__.values()))
+
+def _get_assigned_lambda(node):
+ """ Given a node "x = function() {}", returns "function() {}".
+ """
+ value = None
+ if node.kind == tok.SEMI:
+ assign_node, = node.kids
+ if assign_node and assign_node.kind == tok.ASSIGN:
+ ignored, value = assign_node.kids
+ elif node.kind == tok.VAR:
+ variables = node.kids
+ if variables:
+ value, = variables[-1].kids
+
+ if value and value.kind == tok.FUNCTION and value.opcode == op.ANONFUNOBJ:
+ return value
+
+# TODO: document inspect, node:opcode, etc
+
+warnings = {
+ 'comparison_type_conv': 'comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)',
+ 'default_not_at_end': 'the default case is not at the end of the switch statement',
+ 'duplicate_case_in_switch': 'duplicate case in switch statement',
+ 'missing_default_case': 'missing default case in switch statement',
+ 'with_statement': 'with statement hides undeclared variables; use temporary variable instead',
+ 'useless_comparison': 'useless comparison; comparing identical expressions',
+ 'use_of_label': 'use of label',
+ 'misplaced_regex': 'regular expressions should be preceded by a left parenthesis, assignment, colon, or comma',
+ 'assign_to_function_call': 'assignment to a function call',
+ 'ambiguous_else_stmt': 'the else statement could be matched with one of multiple if statements (use curly braces to indicate intent',
+ 'block_without_braces': 'block statement without curly braces',
+ 'ambiguous_nested_stmt': 'block statements containing block statements should use curly braces to resolve ambiguity',
+ 'inc_dec_within_stmt': 'increment (++) and decrement (--) operators used as part of greater statement',
+ 'comma_separated_stmts': 'multiple statements separated by commas (use semicolons?)',
+ 'empty_statement': 'empty statement or extra semicolon',
+ 'missing_break': 'missing break statement',
+ 'missing_break_for_last_case': 'missing break statement for last case in switch',
+ 'multiple_plus_minus': 'unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs',
+ 'useless_assign': 'useless assignment',
+ 'unreachable_code': 'unreachable code',
+ 'meaningless_block': 'meaningless block; curly braces have no impact',
+ 'useless_void': 'use of the void type may be unnecessary (void is always undefined)',
+ 'parseint_missing_radix': 'parseInt missing radix parameter',
+ 'leading_decimal_point': 'leading decimal point may indicate a number or an object member',
+ 'trailing_decimal_point': 'trailing decimal point may indicate a number or an object member',
+ 'octal_number': 'leading zeros make an octal number',
+ 'trailing_comma_in_array': 'extra comma is not recommended in array initializers',
+ 'useless_quotes': 'the quotation marks are unnecessary',
+ 'mismatch_ctrl_comments': 'mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence',
+ 'redeclared_var': 'redeclaration of {name}',
+ 'undeclared_identifier': 'undeclared identifier: {name}',
+ 'unreferenced_identifier': 'identifier is declared but never referenced: {name}',
+ 'jsl_cc_not_understood': 'couldn\'t understand control comment using /*jsl:keyword*/ syntax',
+ 'nested_comment': 'nested comment',
+ 'legacy_cc_not_understood': 'couldn\'t understand control comment using /*@keyword@*/ syntax',
+ 'var_hides_arg': 'variable {name} hides argument',
+ 'duplicate_formal': 'TODO',
+ 'missing_semicolon': 'missing semicolon',
+ 'missing_semicolon_for_lambda': 'missing semicolon for lambda assignment',
+ 'ambiguous_newline': 'unexpected end of line; it is ambiguous whether these lines are part of the same statement',
+ 'missing_option_explicit': 'the "option explicit" control comment is missing',
+ 'partial_option_explicit': 'the "option explicit" control comment, if used, must be in the first script tag',
+ 'dup_option_explicit': 'duplicate "option explicit" control comment',
+ 'invalid_fallthru': 'unexpected "fallthru" control comment',
+ 'invalid_pass': 'unexpected "pass" control comment',
+ 'want_assign_or_call': 'expected an assignment or function call',
+ 'no_return_value': 'function {name} does not always return a value',
+ 'anon_no_return_value': 'anonymous function does not always return value'
+}
+
+def format_error(errname, **errargs):
+ errdesc = warnings[errname]
+ try:
+ errdesc = re.sub(r"{(\w+)}", lambda match: errargs[match.group(1)], errdesc)
+ except (TypeError, KeyError):...
[truncated message content] |