Thread: [javascriptlint-commit] SF.net SVN: javascriptlint:[258] trunk/javascriptlint
Status: Beta
Brought to you by:
matthiasmiller
From: <mat...@us...> - 2009-10-03 18:10:14
|
Revision: 258 http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=258&view=rev Author: matthiasmiller Date: 2009-10-03 18:10:05 +0000 (Sat, 03 Oct 2009) Log Message: ----------- Implement --help:conf. Modified Paths: -------------- trunk/javascriptlint/conf.py trunk/javascriptlint/jsl.py trunk/javascriptlint/warnings.py Modified: trunk/javascriptlint/conf.py =================================================================== --- trunk/javascriptlint/conf.py 2009-10-03 17:39:40 UTC (rev 257) +++ trunk/javascriptlint/conf.py 2009-10-03 18:10:05 UTC (rev 258) @@ -1,8 +1,90 @@ # vim: ts=4 sw=4 expandtab import os +import unittest import warnings +def _getwarningsconf(): + lines = [] + for name in sorted(warnings.warnings.keys()): + message = warnings.warnings[name] + sign = '+' + if name == 'block_without_braces': + sign = '-' + assert len(name) < 29 + lines.append(sign + name.ljust(29) + '# ' + message) + return '\n'.join(lines) + +DEFAULT_CONF = """\ +# +# Configuration File for JavaScript Lint %(version)s +# Developed by Matthias Miller (http://www.JavaScriptLint.com) +# +# This configuration file can be used to lint a collection of scripts, or to enable +# or disable warnings for scripts that are linted via the command line. +# + +### Warnings +# Enable or disable warnings based on requirements. +# Use "+WarningName" to display or "-WarningName" to suppress. +# +%(warnings)s + + +### Output format +# Customize the format of the error message. +# __FILE__ indicates current file path +# __FILENAME__ indicates current file name +# __LINE__ indicates current line +# __ERROR__ indicates error message +# +# Visual Studio syntax (default): ++output-format __FILE__(__LINE__): __ERROR__ +# Alternative syntax: +#+output-format __FILE__:__LINE__: __ERROR__ + + +### Context +# Show the in-line position of the error. +# Use "+context" to display or "-context" to suppress. +# ++context + + +### Control Comments +# Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for +# the /*@keyword@*/ control comments and JScript conditional comments. (The latter is +# enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, +# although legacy control comments are enabled by default for backward compatibility. +# +-legacy_control_comments + + +### Defining identifiers +# By default, "option explicit" is enabled on a per-file basis. +# To enable this for all files, use "+always_use_option_explicit" ++always_use_option_explicit + +# Define certain identifiers of which the lint is not aware. +# (Use this in conjunction with the "undeclared identifier" warning.) +# +# Common uses for webpages might be: +#+define window +#+define document + + +### Files +# Specify which files to lint +# Use "+recurse" to enable recursion (disabled by default). +# To add a set of files, use "+process FileName", "+process Folder\Path\*.js", +# or "+process Folder\Path\*.htm". +# +""" % { + 'version': '', # TODO + 'warnings': _getwarningsconf(), +} + + class ConfError(Exception): def __init__(self, error): Exception.__init__(self, error) @@ -13,6 +95,12 @@ wants_parm = False wants_dir = False +class DeprecatedSetting(Setting): + wants_parm = False + value = None + def load(self, enabled): + raise ConfError, 'This setting is deprecated.' + class BooleanSetting(Setting): wants_parm = False def __init__(self, default): @@ -54,14 +142,13 @@ 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), + 'lambda_assign_requires_semicolon': DeprecatedSetting(), + 'legacy_control_comments': BooleanSetting(False), + 'jscript_function_extensions': DeprecatedSetting(), + 'always_use_option_explicit': BooleanSetting(True), 'define': DeclareSetting(), - 'context': BooleanSetting(False), + 'context': BooleanSetting(True), 'process': ProcessSetting(recurse), # SpiderMonkey warnings 'no_return_value': BooleanSetting(True), @@ -134,3 +221,14 @@ name = 'define' return self._settings[name].value +class TestConf(unittest.TestCase): + def testDefaultConf(self): + # Make sure the string version corresponds with the code. + fromstr = Conf() + fromstr.loadtext(DEFAULT_CONF) + fromcode = Conf() + settings = set(fromcode._settings.keys() + fromstr._settings.keys()) + for setting in settings: + self.assertEquals(fromcode[setting], fromstr[setting], + 'Mismatched defaults for %s' % setting) + Modified: trunk/javascriptlint/jsl.py =================================================================== --- trunk/javascriptlint/jsl.py 2009-10-03 17:39:40 UTC (rev 257) +++ trunk/javascriptlint/jsl.py 2009-10-03 18:10:05 UTC (rev 258) @@ -81,6 +81,8 @@ help="minimal output") add("--verbose", dest="verbosity", action="store_const", const=2, help="verbose output") + add("--help:conf", dest="showdefaultconf", action="store_true", default=False, + help="display the default configuration file") parser.set_defaults(verbosity=1) options, args = parser.parse_args() @@ -88,6 +90,10 @@ parser.print_help() sys.exit() + if options.showdefaultconf: + print conf.DEFAULT_CONF + sys.exit() + conf_ = conf.Conf() if options.conf: conf_.loadfile(options.conf) @@ -98,7 +104,7 @@ if options.unittest: suite = unittest.TestSuite(); - for module in [htmlparse, jsparse, util]: + for module in [conf, htmlparse, jsparse, util]: suite.addTest(unittest.findTestCases(module)) runner = unittest.TextTestRunner(verbosity=options.verbosity) Modified: trunk/javascriptlint/warnings.py =================================================================== --- trunk/javascriptlint/warnings.py 2009-10-03 17:39:40 UTC (rev 257) +++ trunk/javascriptlint/warnings.py 2009-10-03 18:10:05 UTC (rev 258) @@ -82,7 +82,7 @@ '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', + 'duplicate_formal': 'duplicate formal argument {name}', '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', This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2009-10-22 05:09:32
|
Revision: 279 http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=279&view=rev Author: matthiasmiller Date: 2009-10-22 05:09:12 +0000 (Thu, 22 Oct 2009) Log Message: ----------- htmlparse should not have JavaScript-specific logic. Modified Paths: -------------- trunk/javascriptlint/htmlparse.py trunk/javascriptlint/jsl.py trunk/javascriptlint/lint.py Modified: trunk/javascriptlint/htmlparse.py =================================================================== --- trunk/javascriptlint/htmlparse.py 2009-10-13 01:29:52 UTC (rev 278) +++ trunk/javascriptlint/htmlparse.py 2009-10-22 05:09:12 UTC (rev 279) @@ -2,88 +2,50 @@ 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 = [] + self._tags = [] 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 - } + if tag.lower() == 'script': + attr = dict(attributes) + self._tags.append({ + 'type': 'start', + 'lineno': self.lineno, + 'offset': self.offset, + 'len': len(self.get_starttag_text()), + 'attr': attr + }) 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 + if tag.lower() == 'script': + self._tags.append({ + 'type': 'end', + 'lineno': self.lineno, + 'offset': self.offset, + }) - 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 gettags(self): + return self._tags - 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): +def findscripttags(s): + """ Note that the lineno is 1-based. + """ parser = _Parser() parser.feed(s) parser.close() - return parser.getscripts() + return parser.gettags() 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) + findscripttags(html) + Modified: trunk/javascriptlint/jsl.py =================================================================== --- trunk/javascriptlint/jsl.py 2009-10-13 01:29:52 UTC (rev 278) +++ trunk/javascriptlint/jsl.py 2009-10-22 05:09:12 UTC (rev 279) @@ -104,7 +104,7 @@ if options.unittest: suite = unittest.TestSuite(); - for module in [conf, htmlparse, jsparse, util]: + for module in [conf, htmlparse, jsparse, lint, util]: suite.addTest(unittest.findTestCases(module)) runner = unittest.TextTestRunner(verbosity=options.verbosity) Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2009-10-13 01:29:52 UTC (rev 278) +++ trunk/javascriptlint/lint.py 2009-10-22 05:09:12 UTC (rev 279) @@ -8,6 +8,7 @@ import jsparse import visitation import warnings +import unittest import util from spidermonkey import tok, op @@ -213,6 +214,45 @@ if global_: return global_ +def _findhtmlscripts(contents): + starttag = None + nodepos = jsparse.NodePositions(contents) + for tag in htmlparse.findscripttags(contents): + if tag['type'] == 'start': + # Ignore nested start tags. + if not starttag: + starttag = tag + src = tag['attr'].get('src') + if src: + yield { + 'type': 'external', + 'src': src + } + elif tag['type'] == 'end': + if not starttag: + continue + + # htmlparse returns 1-based line numbers. Calculate the + # position of the script's contents. + tagpos = jsparse.NodePos(starttag['lineno']-1, starttag['offset']) + tagoffset = nodepos.to_offset(tagpos) + startoffset = tagoffset + starttag['len'] + startpos = nodepos.from_offset(startoffset) + endpos = jsparse.NodePos(tag['lineno']-1, tag['offset']) + endoffset = nodepos.to_offset(endpos) + script = contents[startoffset:endoffset] + + if jsparse.is_compilable_unit(script): + starttag = None + if script.strip(): + yield { + 'type': 'inline', + 'pos': startpos, + 'contents': script, + } + else: + assert False, 'Invalid internal tag type %s' % tag['type'] + def lint_files(paths, lint_error, conf=conf.Conf()): def lint_file(path, kind): def import_script(import_path): @@ -235,12 +275,15 @@ if kind == 'js': script_parts.append((None, contents)) elif kind == 'html': - for script in htmlparse.findscripts(contents): - if script['src']: + for script in _findhtmlscripts(contents): + if script['type'] == 'external': other = import_script(script['src']) lint_cache[normpath].importscript(other) - if script['script'].strip(): - script_parts.append((script['startpos'], script['script'])) + elif script['type'] == 'inline': + script_parts.append((script['pos'], script['contents'])) + else: + assert False, 'Invalid internal script type %s' % \ + script['type'] else: assert False, 'Unsupported file kind: %s' % kind @@ -521,3 +564,24 @@ visitor(node) +class TestLint(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.get('src'), x.get('contents')) + for x in _findhtmlscripts(html)] + self.assertEquals(scripts, [ + ('test.js', None), + (None, "<!--\nvar s = '<script></script>';\n-->") + ]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2010-04-23 20:38:12
|
Revision: 298 http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=298&view=rev Author: matthiasmiller Date: 2010-04-23 20:38:04 +0000 (Fri, 23 Apr 2010) Log Message: ----------- Add options to control whether to show header, file listings, and lint summary. Modified Paths: -------------- trunk/javascriptlint/jsl.py trunk/javascriptlint/lint.py Modified: trunk/javascriptlint/jsl.py =================================================================== --- trunk/javascriptlint/jsl.py 2010-04-23 20:09:47 UTC (rev 297) +++ trunk/javascriptlint/jsl.py 2010-04-23 20:38:04 UTC (rev 298) @@ -24,12 +24,12 @@ script = util.readfile(path) jsparse.dump_tree(script) -def _lint(paths, conf_): +def _lint(paths, conf_, printpaths): def lint_error(path, line, col, errname, errdesc): _lint_results['warnings'] = _lint_results['warnings'] + 1 print util.format_error(conf_['output-format'], path, line, col, errname, errdesc) - lint.lint_files(paths, lint_error, conf=conf_) + lint.lint_files(paths, lint_error, conf=conf_, printpaths=printpaths) def _resolve_paths(path, recurse): # Build a list of directories @@ -46,6 +46,11 @@ # force an error to be thrown if no matching files were found. return paths or [path] +def printlogo(): + # TODO: Print version number. + print "JavaScript Lint" + print "Developed by Matthias Miller (http://www.JavaScriptLint.com)" + def _profile_enabled(func, *args, **kwargs): import tempfile import hotshot @@ -83,6 +88,12 @@ help="minimal output") add("--verbose", dest="verbosity", action="store_const", const=2, help="verbose output") + add("--nologo", dest="printlogo", action="store_false", default=True, + help="suppress version information") + add("--nofilelisting", dest="printlisting", action="store_false", + default=True, help="suppress file names") + add("--nosummary", dest="printsummary", action="store_false", default=True, + help="suppress lint summary") add("--help:conf", dest="showdefaultconf", action="store_true", default=False, help="display the default configuration file") parser.set_defaults(verbosity=1) @@ -96,6 +107,9 @@ print conf.DEFAULT_CONF sys.exit() + if options.printlogo: + printlogo() + conf_ = conf.Conf() if options.conf: conf_.loadfile(options.conf) @@ -125,8 +139,12 @@ if options.dump: profile_func(_dump, paths) else: - profile_func(_lint, paths, conf_) + profile_func(_lint, paths, conf_, options.printlisting) + if options.printsummary: + print '\n%i error(s), %i warnings(s)' % (_lint_results['errors'], + _lint_results['warnings']) + if _lint_results['errors']: sys.exit(3) if _lint_results['warnings']: Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2010-04-23 20:09:47 UTC (rev 297) +++ trunk/javascriptlint/lint.py 2010-04-23 20:38:04 UTC (rev 298) @@ -267,7 +267,7 @@ else: assert False, 'Invalid internal tag type %s' % tag['type'] -def lint_files(paths, lint_error, conf=conf.Conf()): +def lint_files(paths, lint_error, conf=conf.Conf(), printpaths=True): def lint_file(path, kind): def import_script(import_path): # The user can specify paths using backslashes (such as when @@ -281,7 +281,8 @@ normpath = util.normpath(path) if normpath in lint_cache: return lint_cache[normpath] - print normpath + if printpaths: + print normpath contents = util.readfile(path) lint_cache[normpath] = _Script() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2011-04-07 00:07:12
|
Revision: 302 http://javascriptlint.svn.sourceforge.net/javascriptlint/?rev=302&view=rev Author: matthiasmiller Date: 2011-04-07 00:07:05 +0000 (Thu, 07 Apr 2011) Log Message: ----------- Move file system functions into separate module in preparation for online lint. Modified Paths: -------------- trunk/javascriptlint/conf.py trunk/javascriptlint/lint.py trunk/javascriptlint/util.py Added Paths: ----------- trunk/javascriptlint/fs.py Modified: trunk/javascriptlint/conf.py =================================================================== --- trunk/javascriptlint/conf.py 2010-10-03 20:18:10 UTC (rev 301) +++ trunk/javascriptlint/conf.py 2011-04-07 00:07:05 UTC (rev 302) @@ -2,6 +2,7 @@ import os import unittest +import fs import util import warnings @@ -186,7 +187,7 @@ def loadfile(self, path): path = os.path.abspath(path) - conf = open(path, 'r').read() + conf = fs.readfile(path) try: self.loadtext(conf, dir=os.path.dirname(path)) except ConfError, error: Added: trunk/javascriptlint/fs.py =================================================================== --- trunk/javascriptlint/fs.py (rev 0) +++ trunk/javascriptlint/fs.py 2011-04-07 00:07:05 UTC (rev 302) @@ -0,0 +1,17 @@ +# vim: ts=4 sw=4 expandtab +import codecs +import os + +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 + Property changes on: trunk/javascriptlint/fs.py ___________________________________________________________________ Added: svn:eol-style + native Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2010-10-03 20:18:10 UTC (rev 301) +++ trunk/javascriptlint/lint.py 2011-04-07 00:07:05 UTC (rev 302) @@ -4,6 +4,7 @@ import re import conf +import fs import htmlparse import jsparse import visitation @@ -295,12 +296,12 @@ def _lint_error(*args): return lint_error(normpath, *args) - normpath = util.normpath(path) + normpath = fs.normpath(path) if normpath in lint_cache: return lint_cache[normpath] if printpaths: print normpath - contents = util.readfile(path) + contents = fs.readfile(path) lint_cache[normpath] = _Script() script_parts = [] Modified: trunk/javascriptlint/util.py =================================================================== --- trunk/javascriptlint/util.py 2010-10-03 20:18:10 UTC (rev 301) +++ trunk/javascriptlint/util.py 2011-04-07 00:07:05 UTC (rev 302) @@ -1,6 +1,5 @@ # vim: ts=4 sw=4 expandtab import cgi -import codecs import os.path import re import unittest @@ -101,19 +100,6 @@ 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('') This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2013-10-03 19:48:28
|
Revision: 332 http://sourceforge.net/p/javascriptlint/code/332 Author: matthiasmiller Date: 2013-10-03 19:48:25 +0000 (Thu, 03 Oct 2013) Log Message: ----------- Gracefully handle IO errors. Modified Paths: -------------- trunk/javascriptlint/lint.py trunk/javascriptlint/warnings.py Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2013-10-03 19:39:07 UTC (rev 331) +++ trunk/javascriptlint/lint.py 2013-10-03 19:48:25 UTC (rev 332) @@ -302,8 +302,13 @@ return lint_cache[normpath] if printpaths: print normpath - contents = fs.readfile(path, encoding) + lint_cache[normpath] = _Script() + try: + contents = fs.readfile(path, encoding) + except IOError, error: + _lint_error(0, 0, 'io_error', unicode(error)) + return lint_cache[normpath] script_parts = [] if kind == 'js': Modified: trunk/javascriptlint/warnings.py =================================================================== --- trunk/javascriptlint/warnings.py 2013-10-03 19:39:07 UTC (rev 331) +++ trunk/javascriptlint/warnings.py 2013-10-03 19:48:25 UTC (rev 332) @@ -114,6 +114,7 @@ 'expected_tok': 'expected token: {token}', 'unexpected_char': 'unexpected character: {char}', 'unexpected_eof': 'unexpected end of file', + 'io_error': '{error}', } def format_error(errname, **errargs): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2014-10-27 22:23:33
|
Revision: 353 http://sourceforge.net/p/javascriptlint/code/353 Author: matthiasmiller Date: 2014-10-27 22:23:26 +0000 (Mon, 27 Oct 2014) Log Message: ----------- Format configuration errors correctly. Modified Paths: -------------- trunk/javascriptlint/conf.py trunk/javascriptlint/jsl.py Modified: trunk/javascriptlint/conf.py =================================================================== --- trunk/javascriptlint/conf.py 2014-10-27 19:42:54 UTC (rev 352) +++ trunk/javascriptlint/conf.py 2014-10-27 22:23:26 UTC (rev 353) @@ -244,7 +244,10 @@ parm = line[len(name):].lstrip() # Load the setting - setting = self._settings[name] + try: + setting = self._settings[name] + except KeyError: + raise ConfError('Unrecognized setting: %s' % name) args = { 'enabled': enabled } Modified: trunk/javascriptlint/jsl.py =================================================================== --- trunk/javascriptlint/jsl.py 2014-10-27 19:42:54 UTC (rev 352) +++ trunk/javascriptlint/jsl.py 2014-10-27 22:23:26 UTC (rev 353) @@ -2,6 +2,7 @@ # vim: ts=4 sw=4 expandtab import codecs import fnmatch +import functools import glob import os import sys @@ -27,12 +28,14 @@ script = fs.readfile(path, encoding) jsparse.dump_tree(script) +def _lint_warning(conf_, path, line, col, errname, errdesc): + _lint_results['warnings'] = _lint_results['warnings'] + 1 + print util.format_error(conf_['output-format'], path, line, col, + errname, errdesc) + def _lint(paths, conf_, printpaths, encoding): - def lint_error(path, line, col, errname, errdesc): - _lint_results['warnings'] = _lint_results['warnings'] + 1 - print util.format_error(conf_['output-format'], path, line, col, - errname, errdesc) - lint.lint_files(paths, lint_error, encoding, conf=conf_, printpaths=printpaths) + lint.lint_files(paths, functools.partial(_lint_warning, conf_), encoding, + conf=conf_, printpaths=printpaths) def _resolve_paths(path, recurse): # Build a list of directories @@ -116,7 +119,11 @@ conf_ = conf.Conf() if options.conf: - conf_.loadfile(options.conf) + try: + conf_.loadfile(options.conf) + except conf.ConfError, error: + _lint_warning(conf_, error.path, error.lineno, 0, 'conf_error', + unicode(error)) profile_func = _profile_disabled if options.profile: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2016-12-23 20:45:43
|
Revision: 355 http://sourceforge.net/p/javascriptlint/code/355 Author: matthiasmiller Date: 2016-12-23 20:45:40 +0000 (Fri, 23 Dec 2016) Log Message: ----------- Fix name conflict with built-in module. Modified Paths: -------------- trunk/javascriptlint/conf.py trunk/javascriptlint/lint.py Added Paths: ----------- trunk/javascriptlint/lintwarnings.py Removed Paths: ------------- trunk/javascriptlint/warnings.py Modified: trunk/javascriptlint/conf.py =================================================================== --- trunk/javascriptlint/conf.py 2014-10-27 22:25:27 UTC (rev 354) +++ trunk/javascriptlint/conf.py 2016-12-23 20:45:40 UTC (rev 355) @@ -5,7 +5,7 @@ import fs import util import version -import warnings +import lintwarnings _DISABLED_WARNINGS = ( 'block_without_braces', @@ -16,8 +16,8 @@ def _getwarningsconf(): lines = [] - for name in sorted(warnings.warnings.keys()): - message = warnings.warnings[name] + for name in sorted(lintwarnings.warnings.keys()): + message = lintwarnings.warnings[name] sign = '+' if name in _DISABLED_WARNINGS: sign = '-' @@ -207,7 +207,7 @@ 'anon_no_return_value': BooleanSetting(True), 'decorate_function_name_warning': BooleanSetting(False), } - for name in warnings.warnings: + for name in lintwarnings.warnings: self._settings[name] = BooleanSetting(True) for warning in _DISABLED_WARNINGS: self.loadline('-%s' % warning) Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2014-10-27 22:25:27 UTC (rev 354) +++ trunk/javascriptlint/lint.py 2016-12-23 20:45:40 UTC (rev 355) @@ -7,8 +7,8 @@ import fs import htmlparse import jsparse +import lintwarnings import visitation -import warnings import unittest import util @@ -482,7 +482,7 @@ # Find all visitors and convert them into "onpush" callbacks that call "report" visitors = { - 'push': warnings.make_visitors(conf) + 'push': lintwarnings.make_visitors(conf) } for event in visitors: for kind, callbacks in visitors[event].items(): @@ -516,11 +516,11 @@ def _lint_script_parts(script_parts, script_cache, lint_error, conf, import_callback): def report_lint(node, errname, offset=0, **errargs): - errdesc = warnings.format_error(errname, **errargs) + errdesc = lintwarnings.format_error(errname, **errargs) _report(offset or node.start_offset, errname, errdesc, True) def report_native(offset, errname, errargs): - errdesc = warnings.format_error(errname, **errargs) + errdesc = lintwarnings.format_error(errname, **errargs) _report(offset, errname, errdesc, False) def _report(offset, errname, errdesc, require_key): @@ -571,7 +571,7 @@ try: ret = visitor(node) assert ret is None, 'visitor should raise an exception, not return a value' - except warnings.LintWarning, warning: + except lintwarnings.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'): Copied: trunk/javascriptlint/lintwarnings.py (from rev 354, trunk/javascriptlint/warnings.py) =================================================================== --- trunk/javascriptlint/lintwarnings.py (rev 0) +++ trunk/javascriptlint/lintwarnings.py 2016-12-23 20:45:40 UTC (rev 355) @@ -0,0 +1,814 @@ +# 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 itertools +import re +import sys +import types + +import util +import visitation + +from jsengine.parser import kind as tok +from jsengine.parser import op + +_ALL_TOKENS = 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', + 'equal_as_assign': 'test for equality (==) mistyped as assignment (=)?', + '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': 'extra comma is not recommended in object initializers', + '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_argument': 'argument declared but never referenced: {name}', + 'unreferenced_function': 'function is declared but never referenced: {name}', + 'unreferenced_variable': 'variable 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', + 'identifier_hides_another': 'identifer {name} hides an identifier in a parent scope', + 'duplicate_formal': 'duplicate formal argument {name}', + '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', + 'unsupported_version': 'JavaScript {version} is not supported', + 'incorrect_version': 'Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version.', + 'for_in_missing_identifier': 'for..in should have identifier on left side', + 'misplaced_function': 'unconventional use of function expression', + 'function_name_missing': 'anonymous function should be named to match property name {name}', + 'function_name_mismatch': 'function name {fn_name} does not match property name {prop_name}', + 'trailing_whitespace': 'trailing whitespace', +} + +errors = { + 'e4x_deprecated': 'e4x is deprecated', + 'semi_before_stmnt': 'missing semicolon before statement', + 'syntax_error': 'syntax error', + 'expected_tok': 'expected token: {token}', + 'unexpected_char': 'unexpected character: {char}', + 'unexpected_eof': 'unexpected end of file', + 'io_error': '{error}', +} + +def format_error(errname, **errargs): + if errname in errors: + errdesc = errors[errname] + else: + errdesc = warnings[errname] + + try: + keyword = re.compile(r"{(\w+)}") + errdesc = keyword.sub(lambda match: errargs[match.group(1)], errdesc) + except (TypeError, KeyError): + raise KeyError('Invalid keyword in error %s: %s' % (errname, errdesc)) + return errdesc + +_visitors = [] +def lookfor(*args): + def decorate(fn): + fn.warning = fn.func_name.rstrip('_') + assert fn.warning in warnings, 'Missing warning description: %s' % fn.warning + + for arg in args: + _visitors.append((arg, fn)) + return decorate + +_visitor_classes = [] +def lookfor_class(*args): + def decorate(cls): + # Convert the class name to camel case + camelcase = re.sub('([A-Z])', r'_\1', cls.__name__).lower().lstrip('_') + + cls.warning = camelcase.rstrip('_') + assert cls.warning in warnings, \ + 'Missing warning description: %s' % cls.warning + + for arg in args: + _visitor_classes.append((arg, cls)) + return decorate + +class LintWarning(Exception): + def __init__(self, node, **errargs): + self.node = node + self.errargs = errargs + +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 == tok.RESERVED and \ + node.parent.parent.kind == tok.FOR: + return node.node_index + return None + +def _get_exit_points(node): + """ Returns a set of exit points, which may be: + * None, indicating a code path with no specific exit point. + * a node of type tok.BREAK, tok.RETURN, tok.THROW. + """ + if node.kind == tok.LC: + exit_points = set([None]) + for kid in node.kids: + if kid: + # Merge in the kid's exit points. + kid_exit_points = _get_exit_points(kid) + exit_points |= kid_exit_points + + # Stop if the kid always exits. + if not None in kid_exit_points: + exit_points.discard(None) + break + elif node.kind == tok.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_) + else: + exit_points.add(None) + elif node.kind == tok.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 == tok.DEFAULT + switch_has_final_fallthru = None in case_exit_points + exit_points |= case_exit_points + + # Correct the "None" exit point. + exit_points.remove(None) + + # Convert "break" into None + def break_to_none(node): + if node and node.kind != tok.BREAK: + return node + exit_points = set(map(break_to_none, exit_points)) + + # 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 == tok.BREAK: + exit_points = set([node]) + elif node.kind == tok.CONTINUE: + exit_points = set([node]) + elif node.kind == tok.WITH: + exit_points = _get_exit_points(node.kids[-1]) + elif node.kind == tok.RETURN: + exit_points = set([node]) + elif node.kind == tok.THROW: + exit_points = set([node]) + elif node.kind == tok.TRY: + try_, catch_, finally_ = node.kids + + exit_points = _get_exit_points(try_) + + if catch_: + assert catch_.kind == tok.RESERVED + catch_, = catch_.kids + assert catch_.kind == tok.LEXICALSCOPE + catch_, = catch_.kids + assert catch_.kind == tok.CATCH + ignored, ignored, catch_ = catch_.kids + assert catch_.kind == tok.LC + + exit_points |= _get_exit_points(catch_) + + if finally_: + finally_exit_points = _get_exit_points(finally_) + if None in finally_exit_points: + # The finally statement does not add a missing exit point. + finally_exit_points.remove(None) + else: + # If the finally statement always returns, the other + # exit points are irrelevant. + if None in exit_points: + exit_points.remove(None) + + exit_points |= finally_exit_points + + else: + exit_points = set([None]) + + return exit_points + +def _loop_has_unreachable_condition(node): + for exit_point in _get_exit_points(node): + if exit_point is None: + return False + if exit_point.kind == tok.CONTINUE: + return False + return True + +@lookfor((tok.EQOP, op.EQ)) +def comparison_type_conv(node): + for kid in node.kids: + if kid.kind == tok.PRIMARY and kid.opcode in (op.NULL, op.TRUE, op.FALSE): + raise LintWarning(kid) + if kid.kind == tok.NUMBER and not kid.dval: + raise LintWarning(kid) + if kid.kind == tok.STRING and not kid.atom: + raise LintWarning(kid) + +@lookfor(tok.DEFAULT) +def default_not_at_end(node): + siblings = node.parent.kids + if node.node_index != len(siblings)-1: + raise LintWarning(siblings[node.node_index+1]) + +@lookfor(tok.CASE) +def duplicate_case_in_switch(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 == tok.CASE: + sibling_value = sibling.kids[0] + if node_value.is_equivalent(sibling_value, True): + raise LintWarning(node) + +@lookfor(tok.SWITCH) +def missing_default_case(node): + value, cases = node.kids + for case in cases.kids: + if case.kind == tok.DEFAULT: + return + raise LintWarning(node) + +@lookfor(tok.WITH) +def with_statement(node): + raise LintWarning(node) + +@lookfor(tok.EQOP, tok.RELOP) +def useless_comparison(node): + for lvalue, rvalue in itertools.combinations(node.kids, 2): + if lvalue.is_equivalent(rvalue): + raise LintWarning(node) + +@lookfor((tok.COLON, op.NAME)) +def use_of_label(node): + raise LintWarning(node) + +@lookfor((tok.OBJECT, op.REGEXP)) +def misplaced_regex(node): + if node.parent.kind == tok.NAME and node.parent.opcode == op.SETNAME: + return # Allow in var statements + if node.parent.kind == tok.ASSIGN and node.parent.opcode == op.NOP: + return # Allow in assigns + if node.parent.kind == tok.COLON and node.parent.parent.kind == tok.RC: + return # Allow in object literals + if node.parent.kind == tok.LP and node.parent.opcode == op.CALL: + return # Allow in parameters + if node.parent.kind == tok.DOT and node.parent.opcode == op.GETPROP: + return # Allow in /re/.property + if node.parent.kind == tok.RETURN: + return # Allow for return values + raise LintWarning(node) + +@lookfor(tok.ASSIGN) +def assign_to_function_call(node): + kid = node.kids[0] + # Unpack parens. + while kid.kind == tok.RP: + kid, = kid.kids + if kid.kind == tok.LP: + raise LintWarning(node) + +@lookfor((tok.ASSIGN, None)) +def equal_as_assign(node): + # Allow in VAR statements. + if node.parent.parent and node.parent.parent.kind == tok.VAR: + return + + if not node.parent.kind in (tok.SEMI, tok.RESERVED, tok.RP, tok.COMMA, + tok.ASSIGN): + raise LintWarning(node) + +@lookfor(tok.IF) +def ambiguous_else_stmt(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 == tok.LC: + return + # Else is only ambiguous in the first branch of an if statement. + if tmp.parent.kind == tok.IF and tmp.node_index == 1: + raise LintWarning(else_) + tmp = tmp.parent + +@lookfor(tok.IF, tok.WHILE, tok.DO, tok.FOR, tok.WITH) +def block_without_braces(node): + if node.kids[1].kind != tok.LC: + raise LintWarning(node.kids[1]) + +_block_nodes = (tok.IF, tok.WHILE, tok.DO, tok.FOR, tok.WITH) +@lookfor(*_block_nodes) +def ambiguous_nested_stmt(node): + # Ignore "else if" + if node.kind == tok.IF and node.node_index == 2 and node.parent.kind == tok.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 tok.LC.) + if node.parent.kind in _block_nodes: + raise LintWarning(node) + +@lookfor(tok.INC, tok.DEC) +def inc_dec_within_stmt(node): + if node.parent.kind == tok.SEMI: + return + + # Allow within the third part of the "for" + tmp = node + while tmp and tmp.parent and tmp.parent.kind == tok.COMMA: + tmp = tmp.parent + if tmp and tmp.node_index == 2 and \ + tmp.parent.kind == tok.RESERVED and \ + tmp.parent.parent.kind == tok.FOR: + return + + raise LintWarning(node) + +@lookfor(tok.COMMA) +def comma_separated_stmts(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 == tok.RB: + return + raise LintWarning(node) + +@lookfor(tok.SEMI) +def empty_statement(node): + if not node.kids[0]: + raise LintWarning(node) +@lookfor(tok.LC) +def empty_statement_(node): + if node.kids: + return + # Ignore the outermost block. + if not node.parent: + return + # Some empty blocks are meaningful. + if node.parent.kind in (tok.CATCH, tok.CASE, tok.DEFAULT, tok.SWITCH, tok.FUNCTION): + return + raise LintWarning(node) + +@lookfor(tok.CASE, tok.DEFAULT) +def missing_break(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 == tok.LC + # Ignore empty case statements + if not case_contents.kids: + return + if None in _get_exit_points(case_contents): + # Show the warning on the *next* node. + raise LintWarning(node.parent.kids[node.node_index+1]) + +@lookfor(tok.CASE, tok.DEFAULT) +def missing_break_for_last_case(node): + if node.node_index < len(node.parent.kids)-1: + return + case_contents = node.kids[1] + assert case_contents.kind == tok.LC + if None in _get_exit_points(case_contents): + raise LintWarning(node) + +@lookfor(tok.INC) +def multiple_plus_minus(node): + if node.node_index == 0 and node.parent.kind == tok.PLUS: + raise LintWarning(node) +@lookfor(tok.DEC) +def multiple_plus_minus_(node): + if node.node_index == 0 and node.parent.kind == tok.MINUS: + raise LintWarning(node) + +@lookfor((tok.NAME, op.SETNAME)) +def useless_assign(node): + if node.parent.kind == tok.ASSIGN: + assert node.node_index == 0 + value = node.parent.kids[1] + elif node.parent.kind == tok.VAR: + value = node.kids[0] + if value and value.kind == tok.NAME and node.atom == value.atom: + raise LintWarning(node) + +@lookfor(tok.BREAK, tok.CONTINUE, tok.RETURN, tok.THROW) +def unreachable_code(node): + if node.parent.kind == tok.LC: + for sibling in node.parent.kids[node.node_index+1:]: + if sibling.kind == tok.VAR: + # Look for a variable assignment + for variable in sibling.kids: + value, = variable.kids + if value: + raise LintWarning(value) + elif sibling.kind == tok.FUNCTION: + # Functions are always declared. + pass + else: + raise LintWarning(sibling) + +@lookfor(tok.FOR) +def unreachable_code_(node): + # Warn if the for loop always exits. + preamble, code = node.kids + if preamble.kind == tok.RESERVED: + pre, condition, post = preamble.kids + if post: + if _loop_has_unreachable_condition(code): + raise LintWarning(post) + +@lookfor(tok.DO) +def unreachable_code__(node): + # Warn if the do..while loop always exits. + code, condition = node.kids + if _loop_has_unreachable_condition(code): + raise LintWarning(condition) + +#TODO: @lookfor(tok.IF) +def meaningless_block(node): + condition, if_, else_ = node.kids + if condition.kind == tok.PRIMARY and condition.opcode in (op.TRUE, op.FALSE, op.NULL): + raise LintWarning(condition) +#TODO: @lookfor(tok.WHILE) +def meaningless_blocK_(node): + condition = node.kids[0] + if condition.kind == tok.PRIMARY and condition.opcode in (op.FALSE, op.NULL): + raise LintWarning(condition) +@lookfor(tok.LC) +def meaningless_block__(node): + if node.parent and node.parent.kind == tok.LC: + raise LintWarning(node) + +@lookfor((tok.UNARYOP, op.VOID)) +def useless_void(node): + raise LintWarning(node) + +@lookfor((tok.LP, op.CALL)) +def parseint_missing_radix(node): + if node.kids[0].kind == tok.NAME and node.kids[0].atom == 'parseInt' and len(node.kids) <= 2: + raise LintWarning(node) + +@lookfor(tok.NUMBER) +def leading_decimal_point(node): + if node.atom.startswith('.'): + raise LintWarning(node) + +@lookfor(tok.NUMBER) +def trailing_decimal_point(node): + if node.parent.kind == tok.DOT: + raise LintWarning(node) + if node.atom.endswith('.'): + raise LintWarning(node) + +_octal_regexp = re.compile('^0[0-9]') +@lookfor(tok.NUMBER) +def octal_number(node): + if _octal_regexp.match(node.atom): + raise LintWarning(node) + +@lookfor(tok.RC) +def trailing_comma(node): + if node.end_comma: + # Warn on the last value in the dictionary. + last_item = node.kids[-1] + assert last_item.kind == tok.COLON + key, value = last_item.kids + raise LintWarning(value) + +@lookfor(tok.RB) +def trailing_comma_in_array(node): + if node.end_comma: + # Warn on the last value in the array. + raise LintWarning(node.kids[-1]) + +@lookfor(tok.STRING) +def useless_quotes(node): + if node.node_index == 0 and node.parent.kind == tok.COLON: + # Only warn if the quotes could safely be removed. + if util.isidentifier(node.atom): + raise LintWarning(node) + +@lookfor(tok.SEMI) +def want_assign_or_call(node): + child, = node.kids + # Ignore empty statements. + if not child: + return + # NOTE: Don't handle comma-separated statements. + if child.kind in (tok.ASSIGN, tok.INC, tok.DEC, tok.DELETE, tok.COMMA): + return + if child.kind == tok.YIELD: + return + # Ignore function calls. + if child.kind == tok.LP and child.opcode == op.CALL: + return + # Allow new function() { } as statements. + if child.kind == tok.NEW: + # The first kid is the constructor, followed by its arguments. + grandchild = child.kids[0] + if grandchild.kind == tok.FUNCTION: + return + raise LintWarning(child) + +def _check_return_value(node): + name = node.fn_name or '(anonymous function)' + + def is_return_with_val(node): + return node and node.kind == tok.RETURN and node.kids[0] + def is_return_without_val(node): + return node and node.kind == tok.RETURN and not node.kids[0] + + node, = node.kids + assert node.kind == tok.LC + + exit_points = _get_exit_points(node) + if filter(is_return_with_val, exit_points): + # If the function returns a value, find all returns without a value. + returns = filter(is_return_without_val, exit_points) + returns.sort(key=lambda node: node.start_offset) + if returns: + raise LintWarning(returns[0], name=name) + # Warn if the function sometimes exits naturally. + if None in exit_points: + raise LintWarning(node, name=name) + +@lookfor(tok.FUNCTION) +def no_return_value(node): + if node.fn_name: + _check_return_value(node) + +@lookfor(tok.FUNCTION) +def anon_no_return_value(node): + if not node.fn_name: + _check_return_value(node) + +@lookfor((tok.FOR, op.FORIN)) +def for_in_missing_identifier(node): + assert node.kids[0].kind == tok.IN + left, right = node.kids[0].kids + if not left.kind in (tok.VAR, tok.NAME): + raise LintWarning(left) + +@lookfor(tok.FUNCTION) +def misplaced_function(node): + # Ignore function statements. + if node.opcode in (None, op.CLOSURE): + return + + # Ignore parens. + parent = node.parent + while parent.kind == tok.RP: + parent = parent.parent + + # Allow x = x || ... + if parent.kind == tok.OR and len(parent.kids) == 2 and \ + node is parent.kids[-1]: + parent = parent.parent + + if parent.kind == tok.NAME and parent.opcode == op.SETNAME: + return # Allow in var statements + if parent.kind == tok.ASSIGN and parent.opcode == op.NOP: + return # Allow in assigns + if parent.kind == tok.COLON and parent.parent.kind == tok.RC: + return # Allow in object literals + if parent.kind == tok.LP and parent.opcode in (op.CALL, op.SETCALL): + return # Allow in parameters + if parent.kind == tok.RETURN: + return # Allow for return values + if parent.kind == tok.NEW: + return # Allow as constructors + raise LintWarning(node) + +def _get_function_property_name(node): + # Ignore function statements. + if node.opcode in (None, op.CLOSURE): + return + + # Ignore parens. + parent = node.parent + while parent.kind == tok.RP: + parent = parent.parent + + # Allow x = x || ... + if parent.kind == tok.OR and len(parent.kids) == 2 and \ + node is parent.kids[-1]: + parent = parent.parent + + # Var assignment. + if parent.kind == tok.NAME and parent.opcode == op.SETNAME: + return parent.atom + + # Assignment. + if parent.kind == tok.ASSIGN and parent.opcode == op.NOP: + if parent.kids[0].kind == tok.NAME and \ + parent.kids[0].opcode == op.SETNAME: + return parent.kids[0].atom + if parent.kids[0].kind == tok.DOT and \ + parent.kids[0].opcode == op.SETPROP: + return parent.kids[0].atom + return '<error>' + + # Object literal. + if parent.kind == tok.COLON and parent.parent.kind == tok.RC: + return parent.kids[0].atom + +def _get_expected_function_name(node, decorate): + name = _get_function_property_name(node) + if name and decorate: + return '__%s' % name + return name + +@lookfor_class(tok.FUNCTION) +class FunctionNameMissing(object): + def __init__(self, conf): + self._decorate = conf['decorate_function_name_warning'] + + def __call__(self, node): + if node.fn_name: + return + + expected_name = _get_expected_function_name(node, self._decorate) + if not expected_name is None: + raise LintWarning(node, name=expected_name) + +@lookfor_class(tok.FUNCTION) +class FunctionNameMismatch(object): + def __init__(self, conf): + self._decorate = conf['decorate_function_name_warning'] + + def __call__(self, node): + if not node.fn_name: + return + + expected_name = _get_expected_function_name(node, self._decorate) + if expected_name is None: + return + + if expected_name != node.fn_name: + raise LintWarning(node, fn_name=node.fn_name, + prop_name=expected_name) + +@lookfor() +def mismatch_ctrl_comments(node): + pass + +@lookfor() +def redeclared_var(node): + pass + +@lookfor() +def undeclared_identifier(node): + pass + +@lookfor() +def jsl_cc_not_understood(node): + pass + +@lookfor() +def nested_comment(node): + pass + +@lookfor() +def legacy_cc_not_understood(node): + pass + +@lookfor() +def var_hides_arg(node): + pass + +@lookfor() +def duplicate_formal(node): + pass + +@lookfor(*_ALL_TOKENS) +def missing_semicolon(node): + if node.no_semi: + if not _get_assigned_lambda(node): + raise LintWarning(node) + +@lookfor(*_ALL_TOKENS) +def missing_semicolon_for_lambda(node): + if node.no_semi: + # spidermonkey sometimes returns incorrect positions for var + # statements, so use the position of the lambda instead. + lambda_ = _get_assigned_lambda(node) + if lambda_: + raise LintWarning(lambda_) + +@lookfor() +def ambiguous_newline(node): + pass + +@lookfor() +def missing_option_explicit(node): + pass + +@lookfor() +def partial_option_explicit(node): + pass + +@lookfor() +def dup_option_explicit(node): + pass + +def make_visitors(conf): + all_visitors = list(_visitors) + for kind, klass in _visitor_classes: + all_visitors.append((kind, klass(conf=conf))) + + visitors = {} + for kind, func in all_visitors: + try: + visitors[kind].append(func) + except KeyError: + visitors[kind] = [func] + return visitors + Deleted: trunk/javascriptlint/warnings.py =================================================================== --- trunk/javascriptlint/warnings.py 2014-10-27 22:25:27 UTC (rev 354) +++ trunk/javascriptlint/warnings.py 2016-12-23 20:45:40 UTC (rev 355) @@ -1,814 +0,0 @@ -# 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 itertools -import re -import sys -import types - -import util -import visitation - -from jsengine.parser import kind as tok -from jsengine.parser import op - -_ALL_TOKENS = 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', - 'equal_as_assign': 'test for equality (==) mistyped as assignment (=)?', - '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': 'extra comma is not recommended in object initializers', - '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_argument': 'argument declared but never referenced: {name}', - 'unreferenced_function': 'function is declared but never referenced: {name}', - 'unreferenced_variable': 'variable 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', - 'identifier_hides_another': 'identifer {name} hides an identifier in a parent scope', - 'duplicate_formal': 'duplicate formal argument {name}', - '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', - 'unsupported_version': 'JavaScript {version} is not supported', - 'incorrect_version': 'Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version.', - 'for_in_missing_identifier': 'for..in should have identifier on left side', - 'misplaced_function': 'unconventional use of function expression', - 'function_name_missing': 'anonymous function should be named to match property name {name}', - 'function_name_mismatch': 'function name {fn_name} does not match property name {prop_name}', - 'trailing_whitespace': 'trailing whitespace', -} - -errors = { - 'e4x_deprecated': 'e4x is deprecated', - 'semi_before_stmnt': 'missing semicolon before statement', - 'syntax_error': 'syntax error', - 'expected_tok': 'expected token: {token}', - 'unexpected_char': 'unexpected character: {char}', - 'unexpected_eof': 'unexpected end of file', - 'io_error': '{error}', -} - -def format_error(errname, **errargs): - if errname in errors: - errdesc = errors[errname] - else: - errdesc = warnings[errname] - - try: - keyword = re.compile(r"{(\w+)}") - errdesc = keyword.sub(lambda match: errargs[match.group(1)], errdesc) - except (TypeError, KeyError): - raise KeyError('Invalid keyword in error %s: %s' % (errname, errdesc)) - return errdesc - -_visitors = [] -def lookfor(*args): - def decorate(fn): - fn.warning = fn.func_name.rstrip('_') - assert fn.warning in warnings, 'Missing warning description: %s' % fn.warning - - for arg in args: - _visitors.append((arg, fn)) - return decorate - -_visitor_classes = [] -def lookfor_class(*args): - def decorate(cls): - # Convert the class name to camel case - camelcase = re.sub('([A-Z])', r'_\1', cls.__name__).lower().lstrip('_') - - cls.warning = camelcase.rstrip('_') - assert cls.warning in warnings, \ - 'Missing warning description: %s' % cls.warning - - for arg in args: - _visitor_classes.append((arg, cls)) - return decorate - -class LintWarning(Exception): - def __init__(self, node, **errargs): - self.node = node - self.errargs = errargs - -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 == tok.RESERVED and \ - node.parent.parent.kind == tok.FOR: - return node.node_index - return None - -def _get_exit_points(node): - """ Returns a set of exit points, which may be: - * None, indicating a code path with no specific exit point. - * a node of type tok.BREAK, tok.RETURN, tok.THROW. - """ - if node.kind == tok.LC: - exit_points = set([None]) - for kid in node.kids: - if kid: - # Merge in the kid's exit points. - kid_exit_points = _get_exit_points(kid) - exit_points |= kid_exit_points - - # Stop if the kid always exits. - if not None in kid_exit_points: - exit_points.discard(None) - break - elif node.kind == tok.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_) - else: - exit_points.add(None) - elif node.kind == tok.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 == tok.DEFAULT - switch_has_final_fallthru = None in case_exit_points - exit_points |= case_exit_points - - # Correct the "None" exit point. - exit_points.remove(None) - - # Convert "break" into None - def break_to_none(node): - if node and node.kind != tok.BREAK: - return node - exit_points = set(map(break_to_none, exit_points)) - - # 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 == tok.BREAK: - exit_points = set([node]) - elif node.kind == tok.CONTINUE: - exit_points = set([node]) - elif node.kind == tok.WITH: - exit_points = _get_exit_points(node.kids[-1]) - elif node.kind == tok.RETURN: - exit_points = set([node]) - elif node.kind == tok.THROW: - exit_points = set([node]) - elif node.kind == tok.TRY: - try_, catch_, finally_ = node.kids - - exit_points = _get_exit_points(try_) - - if catch_: - assert catch_.kind == tok.RESERVED - catch_, = catch_.kids - assert catch_.kind == tok.LEXICALSCOPE - catch_, = catch_.kids - assert catch_.kind == tok.CATCH - ignored, ignored, catch_ = catch_.kids - assert catch_.kind == tok.LC - - exit_points |= _get_exit_points(catch_) - - if finally_: - finally_exit_points = _get_exit_points(finally_) - if None in finally_exit_points: - # The finally statement does not add a missing exit point. - finally_exit_points.remove(None) - else: - # If the finally statement always returns, the other - # exit points are irrelevant. - if None in exit_points: - exit_points.remove(None) - - exit_points |= finally_exit_points - - else: - exit_points = set([None]) - - return exit_points - -def _loop_has_unreachable_condition(node): - for exit_point in _get_exit_points(node): - if exit_point is None: - return False - if exit_point.kind == tok.CONTINUE: - return False - return True - -@lookfor((tok.EQOP, op.EQ)) -def comparison_type_conv(node): - for kid in node.kids: - if kid.kind == tok.PRIMARY and kid.opcode in (op.NULL, op.TRUE, op.FALSE): - raise LintWarning(kid) - if kid.kind == tok.NUMBER and not kid.dval: - raise LintWarning(kid) - if kid.kind == tok.STRING and not kid.atom: - raise LintWarning(kid) - -@lookfor(tok.DEFAULT) -def default_not_at_end(node): - siblings = node.parent.kids - if node.node_index != len(siblings)-1: - raise LintWarning(siblings[node.node_index+1]) - -@lookfor(tok.CASE) -def duplicate_case_in_switch(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 == tok.CASE: - sibling_value = sibling.kids[0] - if node_value.is_equivalent(sibling_value, True): - raise LintWarning(node) - -@lookfor(tok.SWITCH) -def missing_default_case(node): - value, cases = node.kids - for case in cases.kids: - if case.kind == tok.DEFAULT: - return - raise LintWarning(node) - -@lookfor(tok.WITH) -def with_statement(node): - raise LintWarning(node) - -@lookfor(tok.EQOP, tok.RELOP) -def useless_comparison(node): - for lvalue, rvalue in itertools.combinations(node.kids, 2): - if lvalue.is_equivalent(rvalue): - raise LintWarning(node) - -@lookfor((tok.COLON, op.NAME)) -def use_of_label(node): - raise LintWarning(node) - -@lookfor((tok.OBJECT, op.REGEXP)) -def misplaced_regex(node): - if node.parent.kind == tok.NAME and node.parent.opcode == op.SETNAME: - return # Allow in var statements - if node.parent.kind == tok.ASSIGN and node.parent.opcode == op.NOP: - return # Allow in assigns - if node.parent.kind == tok.COLON and node.parent.parent.kind == tok.RC: - return # Allow in object literals - if node.parent.kind == tok.LP and node.parent.opcode == op.CALL: - return # Allow in parameters - if node.parent.kind == tok.DOT and node.parent.opcode == op.GETPROP: - return # Allow in /re/.property - if node.parent.kind == tok.RETURN: - return # Allow for return values - raise LintWarning(node) - -@lookfor(tok.ASSIGN) -def assign_to_function_call(node): - kid = node.kids[0] - # Unpack parens. - while kid.kind == tok.RP: - kid, = kid.kids - if kid.kind == tok.LP: - raise LintWarning(node) - -@lookfor((tok.ASSIGN, None)) -def equal_as_assign(node): - # Allow in VAR statements. - if node.parent.parent and node.parent.parent.kind == tok.VAR: - return - - if not node.parent.kind in (tok.SEMI, tok.RESERVED, tok.RP, tok.COMMA, - tok.ASSIGN): - raise LintWarning(node) - -@lookfor(tok.IF) -def ambiguous_else_stmt(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 == tok.LC: - return - # Else is only ambiguous in the first branch of an if statement. - if tmp.parent.kind == tok.IF and tmp.node_index == 1: - raise LintWarning(else_) - tmp = tmp.parent - -@lookfor(tok.IF, tok.WHILE, tok.DO, tok.FOR, tok.WITH) -def block_without_braces(node): - if node.kids[1].kind != tok.LC: - raise LintWarning(node.kids[1]) - -_block_nodes = (tok.IF, tok.WHILE, tok.DO, tok.FOR, tok.WITH) -@lookfor(*_block_nodes) -def ambiguous_nested_stmt(node): - # Ignore "else if" - if node.kind == tok.IF and node.node_index == 2 and node.parent.kind == tok.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 tok.LC.) - if node.parent.kind in _block_nodes: - raise LintWarning(node) - -@lookfor(tok.INC, tok.DEC) -def inc_dec_within_stmt(node): - if node.parent.kind == tok.SEMI: - return - - # Allow within the third part of the "for" - tmp = node - while tmp and tmp.parent and tmp.parent.kind == tok.COMMA: - tmp = tmp.parent - if tmp and tmp.node_index == 2 and \ - tmp.parent.kind == tok.RESERVED and \ - tmp.parent.parent.kind == tok.FOR: - return - - raise LintWarning(node) - -@lookfor(tok.COMMA) -def comma_separated_stmts(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 == tok.RB: - return - raise LintWarning(node) - -@lookfor(tok.SEMI) -def empty_statement(node): - if not node.kids[0]: - raise LintWarning(node) -@lookfor(tok.LC) -def empty_statement_(node): - if node.kids: - return - # Ignore the outermost block. - if not node.parent: - return - # Some empty blocks are meaningful. - if node.parent.kind in (tok.CATCH, tok.CASE, tok.DEFAULT, tok.SWITCH, tok.FUNCTION): - return - raise LintWarning(node) - -@lookfor(tok.CASE, tok.DEFAULT) -def missing_break(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 == tok.LC - # Ignore empty case statements - if not case_contents.kids: - return - if None in _get_exit_points(case_contents): - # Show the warning on the *next* node. - raise LintWarning(node.parent.kids[node.node_index+1]) - -@lookfor(tok.CASE, tok.DEFAULT) -def missing_break_for_last_case(node): - if node.node_index < len(node.parent.kids)-1: - return - case_contents = node.kids[1] - assert case_contents.kind == tok.LC - if None in _get_exit_points(case_contents): - raise LintWarning(node) - -@lookfor(tok.INC) -def multiple_plus_minus(node): - if node.node_index == 0 and node.parent.kind == tok.PLUS: - raise LintWarning(node) -@lookfor(tok.DEC) -def multiple_plus_minus_(node): - if node.node_index == 0 and node.parent.kind == tok.MINUS: - raise LintWarning(node) - -@lookfor((tok.NAME, op.SETNAME)) -def useless_assign(node): - if node.parent.kind == tok.ASSIGN: - assert node.node_index == 0 - value = node.parent.kids[1] - elif node.parent.kind == tok.VAR: - value = node.kids[0] - if value and value.kind == tok.NAME and node.atom == value.atom: - raise LintWarning(node) - -@lookfor(tok.BREAK, tok.CONTINUE, tok.RETURN, tok.THROW) -def unreachable_code(node): - if node.parent.kind == tok.LC: - for sibling in node.parent.kids[node.node_index+1:]: - if sibling.kind == tok.VAR: - # Look for a variable assignment - for variable in sibling.kids: - value, = variable.kids - if value: - raise LintWarning(value) - elif sibling.kind == tok.FUNCTION: - # Functions are always declared. - pass - else: - raise LintWarning(sibling) - -@lookfor(tok.FOR) -def unreachable_code_(node): - # Warn if the for loop always exits. - preamble, code = node.kids - if preamble.kind == tok.RESERVED: - pre, condition, post = preamble.kids - if post: - if _loop_has_unreachable_condition(code): - raise LintWarning(post) - -@lookfor(tok.DO) -def unreachable_code__(node): - # Warn if the do..while loop always exits. - code, condition = node.kids - if _loop_has_unreachable_condition(code): - raise LintWarning(condition) - -#TODO: @lookfor(tok.IF) -def meaningless_block(node): - condition, if_, else_ = node.kids - if condition.kind == tok.PRIMARY and condition.opcode in (op.TRUE, op.FALSE, op.NULL): - raise LintWarning(condition) -#TODO: @lookfor(tok.WHILE) -def meaningless_blocK_(node): - condition = node.kids[0] - if condition.kind == tok.PRIMARY and condition.opcode in (op.FALSE, op.NULL): - raise LintWarning(condition) -@lookfor(tok.LC) -def meaningless_block__(node): - if node.parent and node.parent.kind == tok.LC: - raise LintWarning(node) - -@lookfor((tok.UNARYOP, op.VOID)) -def useless_void(node): - raise LintWarning(node) - -@lookfor((tok.LP, op.CALL)) -def parseint_missing_radix(node): - if node.kids[0].kind == tok.NAME and node.kids[0].atom == 'parseInt' and len(node.kids) <= 2: - raise LintWarning(node) - -@lookfor(tok.NUMBER) -def leading_decimal_point(node): - if node.atom.startswith('.'): - raise LintWarning(node) - -@lookfor(tok.NUMBER) -def trailing_decimal_point(node): - if node.parent.kind == tok.DOT: - raise LintWarning(node) - if node.atom.endswith('.'): - raise LintWarning(node) - -_octal_regexp = re.compile('^0[0-9]') -@lookfor(tok.NUMBER) -def octal_number(node): - if _octal_regexp.match(node.atom): - raise LintWarning(node) - -@lookfor(tok.RC) -def trailing_comma(node): - if node.end_comma: - # Warn on the last value in the dictionary. - last_item = node.kids[-1] - assert last_item.kind == tok.COLON - key, value = last_item.kids - raise LintWarning(value) - -@lookfor(tok.RB) -def trailing_comma_in_array(node): - if node.end_comma: - # Warn on the last value in the array. - raise LintWarning(node.kids[-1]) - -@lookfor(tok.STRING) -def useless_quotes(node): - if node.node_index == 0 and node.parent.kind == tok.COLON: - # Only warn if the quotes could safely be removed. - if util.isidentifier(node.atom): - raise LintWarning(node) - -@lookfor(tok.SEMI) -def want_assign_or_call(node): - child, = node.kids - # Ignore empty statements. - if not child: - return - # NOTE: Don't handle comma-separated statements. - if child.kind in (tok.ASSIGN, tok.INC, tok.DEC, tok.DELETE, tok.COMMA): - return - if child.kind == tok.YIELD: - return - # Ignore function calls. - if child.kind == tok.LP and child.opcode == op.CALL: - return - # Allow new function() { } as statements. - if child.kind == tok.NEW: - # The first kid is the constructor, followed by its arguments. - grandchild = child.kids[0] - if grandchild.kind == tok.FUNCTION: - return - raise LintWarning(child) - -def _check_return_value(node): - name = node.fn_name or '(anonymous function)' - - def is_return_with_val(node): - return node and node.kind == tok.RETURN and node.kids[0] - def is_return_without_val(node): - return node and node.kind == tok.RETURN and not node.kids[0] - - node, = node.kids - assert node.kind == tok.LC - - exit_points = _get_exit_points(node) - if filter(is_return_with_val, exit_points): - # If the function returns a value, find all returns without a value. - returns = filter(is_return_without_val, exit_points) - returns.sort(key=lambda node: node.start_offset) - if returns: - raise LintWarning(returns[0], name=name) - # Warn if the function sometimes exits naturally. - if None in exit_points: - raise LintWarning(node, name=name) - -@lookfor(tok.FUNCTION) -def no_return_value(node): - if node.fn_name: - _check_return_value(node) - -@lookfor(tok.FUNCTION) -def anon_no_return_value(node): - if not node.fn_name: - _check_return_value(node) - -@lookfor((tok.FOR, op.FORIN)) -def for_in_missing_identifier(node): - assert node.kids[0].kind == tok.IN - left, right = node.kids[0].kids - if not left.kind in (tok.VAR, tok.NAME): - raise LintWarning(left) - -@lookfor(tok.FUNCTION) -def misplaced_function(node): - # Ignore function statements. - if node.opcode in (None, op.CLOSURE): - return - - # Ignore parens. - parent = node.parent - while parent.kind == tok.RP: - parent = parent.parent - - # Allow x = x || ... - if parent.kind == tok.OR and len(parent.kids) == 2 and \ - node is parent.kids[-1]: - parent = parent.parent - - if parent.kind == tok.NAME and parent.opcode == op.SETNAME: - return # Allow in var statements - if parent.kind == tok.ASSIGN and parent.opcode == op.NOP: - return # Allow in assigns - if parent.kind == tok.COLON and parent.parent.kind == tok.RC: - return # Allow in object literals - if parent.kind == tok.LP and parent.opcode in (op.CALL, op.SETCALL): - return # Allow in parameters - if parent.kind == tok.RETURN: - return # Allow for return values - if parent.kind == tok.NEW: - return # Allow as constructors - raise LintWarning(node) - -def _get_function_property_name(node): - # Ignore function statements. - if node.opcode in (None, op.CLOSURE): - return - - # Ignore parens. - parent = node.parent - while parent.kind == tok.RP: - parent = parent.parent - - # Allow x = x || ... - if parent.kind == tok.OR and len(parent.kids) == 2 and \ - node is parent.kids[-1]: - parent = parent.parent - - # Var assignment. - if parent.kind == tok.NAME and parent.opcode == op.SETNAME: - return parent.atom - - # Assignment. - if parent.kind == tok.ASSIGN and parent.opcode == op.NOP: - if parent.kids[0].kind == tok.NAME and \ - parent.kids[0].opcode == op.SETNAME: - return parent.kids[0].atom - if parent.kids[0].kind == tok.DOT and \ - parent.kids[0].opcode == op.SETPROP: - return parent.kids[0].atom - return '<error>' - - # Object literal. - if parent.kind == tok.COLON and parent.parent.kind == tok.RC: - return parent.kids[0].atom - -def _get_expected_function_name(node, decorate): - name = _get_function_property_name(node) - if name and decorate: - return '__%s' % name - return name - -@lookfor_class(tok.FUNCTION) -class FunctionNameMissing(object): - def __init__(self, conf): - self._decorate = conf['decorate_function_name_warning'] - - def __call__(self, node): - if node.fn_name: - return - - expected_name = _get_expected_function_name(node, self._decorate) - if not expected_name is None: - raise LintWarning(node, name=expected_name) - -@lookfor_class(tok.FUNCTION) -class FunctionNameMismatch(object): - def __init__(self, conf): - self._decorate = conf['decorate_function_name_warning'] - - def __call__(self, node): - if not node.fn_name: - return - - expected_name = _get_expected_function_name(node, self._decorate) - if expected_name is None: - return - - if expected_name != node.fn_name: - raise LintWarning(node, fn_name=node.fn_name, - prop_name=expected_name) - -@lookfor() -def mismatch_ctrl_comments(node): - pass - -@lookfor() -def redeclared_var(node): - pass - -@lookfor() -def undeclared_identifier(node): - pass - -@lookfor() -def jsl_cc_not_understood(node): - pass - -@lookfor() -def nested_comment(node): - pass - -@lookfor() -def legacy_cc_not_understood(node): - pass - -@lookfor() -def var_hides_arg(node): - pass - -@lookfor() -def duplicate_formal(node): - pass - -@lookfor(*_ALL_TOKENS) -def missing_semicolon(node): - if node.no_semi: - if not _get_assigned_lambda(node): - raise LintWarning(node) - -@lookfor(*_ALL_TOKENS) -def missing_semicolon_for_lambda(node): - if node.no_semi: - # spidermonkey sometimes returns incorrect positions for var - # statements, so use the position of the lambda instead. - lambd... [truncated message content] |
From: <mat...@us...> - 2016-12-23 22:27:22
|
Revision: 359 http://sourceforge.net/p/javascriptlint/code/359 Author: matthiasmiller Date: 2016-12-23 22:27:20 +0000 (Fri, 23 Dec 2016) Log Message: ----------- Remove more unused code. Modified Paths: -------------- trunk/javascriptlint/lint.py trunk/javascriptlint/lintwarnings.py Removed Paths: ------------- trunk/javascriptlint/visitation.py Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2016-12-23 21:56:37 UTC (rev 358) +++ trunk/javascriptlint/lint.py 2016-12-23 22:27:20 UTC (rev 359) @@ -8,7 +8,6 @@ import htmlparse import jsparse import lintwarnings -import visitation import unittest import util @@ -478,7 +477,7 @@ 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)]) + _get_scope_checks(visitors, script_cache.scope, report) # kickoff! _lint_node(root, visitors) @@ -582,43 +581,44 @@ else: scope.add_declaration(name, node, type_) -def _get_scope_checks(scope, report): +def _get_scope_checks(visitors, scope, report): scopes = [scope] - class scope_checks: - """ This is a non-standard visitation class to track scopes. The - docstring is unused since this class never throws lint errors. - """ - @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, 'var', node, report) - return - if node.parent.kind == tok.CATCH: - scopes[-1].add_declaration(node.atom, node, 'var') - scopes[-1].add_reference(node.atom, node) + def _visit(event, *args): + def _decorate(fn): + for arg in args: + visitors.setdefault(event, {}).setdefault(arg, []).append(fn) + return fn + return _decorate - @visitation.visit('push', tok.FUNCTION) - def _push_func(self, node): - if node.opcode in (None, op.CLOSURE) and node.fn_name: - _warn_or_declare(scopes[-1], node.fn_name, 'function', node, report) - self._push_scope(node) - for var_name in node.fn_args: - if scopes[-1].has_property(var_name.atom): - report(var_name, 'duplicate_formal', name=var_name.atom) - scopes[-1].add_declaration(var_name.atom, var_name, 'arg') + @_visit('push', tok.NAME) + def _push_name(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, 'var', node, report) + return + if node.parent.kind == tok.CATCH: + scopes[-1].add_declaration(node.atom, node, 'var') + scopes[-1].add_reference(node.atom, node) - @visitation.visit('push', tok.LEXICALSCOPE, tok.WITH) - def _push_scope(self, node): - scopes.append(scopes[-1].add_scope(node)) + @_visit('push', tok.FUNCTION) + def _push_func(node): + if node.opcode in (None, op.CLOSURE) and node.fn_name: + _warn_or_declare(scopes[-1], node.fn_name, 'function', node, report) + _push_scope(node) + for var_name in node.fn_args: + if scopes[-1].has_property(var_name.atom): + report(var_name, 'duplicate_formal', name=var_name.atom) + scopes[-1].add_declaration(var_name.atom, var_name, 'arg') - @visitation.visit('pop', tok.FUNCTION, tok.LEXICALSCOPE, tok.WITH) - def _pop_scope(self, node): - scopes.pop() + @_visit('push', tok.LEXICALSCOPE, tok.WITH) + def _push_scope(node): + scopes.append(scopes[-1].add_scope(node)) - return scope_checks + @_visit('pop', tok.FUNCTION, tok.LEXICALSCOPE, tok.WITH) + def _pop_scope(node): + scopes.pop() def _lint_node(node, visitors): Modified: trunk/javascriptlint/lintwarnings.py =================================================================== --- trunk/javascriptlint/lintwarnings.py 2016-12-23 21:56:37 UTC (rev 358) +++ trunk/javascriptlint/lintwarnings.py 2016-12-23 22:27:20 UTC (rev 359) @@ -20,7 +20,6 @@ import types import util -import visitation from jsengine.parser import kind as tok from jsengine.parser import op Deleted: trunk/javascriptlint/visitation.py =================================================================== --- trunk/javascriptlint/visitation.py 2016-12-23 21:56:37 UTC (rev 358) +++ trunk/javascriptlint/visitation.py 2016-12-23 22:27:20 UTC (rev 359) @@ -1,52 +0,0 @@ -# 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 %s' % klass.__name__) - - # 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 - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mat...@us...> - 2016-12-23 23:46:57
|
Revision: 364 http://sourceforge.net/p/javascriptlint/code/364 Author: matthiasmiller Date: 2016-12-23 23:46:55 +0000 (Fri, 23 Dec 2016) Log Message: ----------- e4x_deprecated should be a warning, not an error. Modified Paths: -------------- trunk/javascriptlint/jsparse.py trunk/javascriptlint/lint.py trunk/javascriptlint/lintwarnings.py Modified: trunk/javascriptlint/jsparse.py =================================================================== --- trunk/javascriptlint/jsparse.py 2016-12-23 23:38:49 UTC (rev 363) +++ trunk/javascriptlint/jsparse.py 2016-12-23 23:46:55 UTC (rev 364) @@ -58,8 +58,6 @@ assert not start_offset is None jsversion = jsversion or JSVersion.default() assert isvalidversion(jsversion), jsversion - if jsversion.e4x: - error_callback(start_offset, 'e4x_deprecated', {}) return jsengine.parser.parse(script, jsversion.version, error_callback, start_offset) Modified: trunk/javascriptlint/lint.py =================================================================== --- trunk/javascriptlint/lint.py 2016-12-23 23:38:49 UTC (rev 363) +++ trunk/javascriptlint/lint.py 2016-12-23 23:46:55 UTC (rev 364) @@ -417,6 +417,10 @@ version=jsversion.version) return + if jsversion.e4x: + report_lint(None, 'e4x_deprecated', + jsversionnode.start_offset if jsversionnode else script_offset) + root = jsparse.parse(script, jsversion, parse_error, script_offset) if not root: # Report errors and quit. Modified: trunk/javascriptlint/lintwarnings.py =================================================================== --- trunk/javascriptlint/lintwarnings.py 2016-12-23 23:38:49 UTC (rev 363) +++ trunk/javascriptlint/lintwarnings.py 2016-12-23 23:46:55 UTC (rev 364) @@ -103,10 +103,10 @@ 'function_name_missing': 'anonymous function should be named to match property name {name}', 'function_name_mismatch': 'function name {fn_name} does not match property name {prop_name}', 'trailing_whitespace': 'trailing whitespace', + 'e4x_deprecated': 'e4x is deprecated', } errors = { - 'e4x_deprecated': 'e4x is deprecated', 'semi_before_stmnt': 'missing semicolon before statement', 'syntax_error': 'syntax error', 'expected_tok': 'expected token: {token}', This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |