Adding JSLint to ReviewBot

Review Request #3435 — Created Oct. 21, 2012 and discarded

Information

ReviewBot

Reviewers

Implementation of the JSLint tool for ReviewBot. Parses all uploaded files to a review request and outputs descriptive comments on the relevant .js source files on lines where style errors are found. Options for this tool can be configured in the tool's admin panel. This addition to ReviewBot requires that SpiderMonkey be installed (other engines might also be suitable but have not been tested) to enable the running of the jslint.js linting tool.
manual testing:
- tested jslint on the spidermonkey commandline to make sure that the script itself was working as expected locally.
- tested what error output the jslint.com website provides for a given file and tested ReviewBot on local dev server tool by posting a review with same file and ensuring output is the same with the same options ticked (seeing that file with errors was being commented by ReviewBot).
- started by importing all of the dependencies used by the jslint.com website and removed all which were not necessary to the standalone jslint tool, testing that it yielded the same results. Those other dependencies were only necessary for web purposes, even if Doug Crockford suggests they be bundled with jslint itself and minified.
- tested changing options in the option panel, and making sure they affect the output of the static analysis tool in locally published reviews. Included print statements in py and js scripts to make sure the correct options were being passed into jslint.
Description From Last Updated

This should probably be renamed. JSLint is more than a style checker.

SM smacleod

This should probably be adjusted like 'name'. It might be a good idea to include the version number of JSLint …

SM smacleod

This should probably be changed to be case insensitive. I've seen some use '.JS' I really would like to add …

SM smacleod

This appears to still be just copied from the pep8 tool, but you'll need to change this parsing code to …

SM smacleod

let's put these in alphabetical order.

SM smacleod

As Aamir pointed out, 'import os' should be coming first. Imports should be ordered like this: Standard Library imports (e.g. …

SM smacleod

If this is enabled, I think you should definitely indicate in the review body_top, or body_bottom that the scan was …

SM smacleod

This should probably be done only once per request, instead of every time handle_file is called (Once per file). You'll …

SM smacleod

I don't think this is quite correct. The package key should probably be 'reviewbot', or 'reviewbot.tools'. I'm fine with lib …

SM smacleod

Let's rename jsLint.py to jslint.py. So, this should be changed to 'jslint = reviewbot.tools.jslint:JSLintTool',

SM smacleod

I believe, generally, we prefer the higher level imports to go above the more granular ones, so line 3 and …

mike_conleymike_conley

Evil? Or Eval? Or Evil-Eval? Not sure if this is being cheeky, or just a typo. :)

mike_conleymike_conley

Isn't 80 the traditional character limit? Or is something here 0-indexed? Or have I just been lied to all my …

mike_conleymike_conley

PACKAGE_ROOT is in all caps, and lib_path is not. Why?

mike_conleymike_conley

Can you not simply pass the actual True and False literals to the script? Will the JS engine accept something …

mike_conleymike_conley

Might not be necessary to sent the lowercase form of the strings. runJSLint is checking the content of the string …

AL Allyshia

Yeah, maybe this is overkill then.

mike_conleymike_conley

This line will need to be indented two more spaces, since it's still inside the dicts parens.

mike_conleymike_conley

Is there a reason this is not called *before* we do all of JSLineTool's specialized stuff?

mike_conleymike_conley

Let's put a newline after the if-block.

mike_conleymike_conley

I don't think the string representation of dict for the things you're passing is going to change anytime soon, so …

mike_conleymike_conley

self.jsLintOptions is a dict. wondering if we should depend on auto-formatting of dict to string used here or whether we …

AL Allyshia

Yeah, a little less silent-fail would probably be good here.

mike_conleymike_conley

This function should be documented. Also, as a general rule, we tend to use 4-space indentation for Javascript.

mike_conleymike_conley

var options

mike_conleymike_conley

Should be formatted like: if (custom_options) { ... }

mike_conleymike_conley

for (var op in custom_options) {

mike_conleymike_conley

var custom

mike_conleymike_conley

See comment on line 127 of jslint.py. Originally this check did not exist, and we passed the straight string value …

AL Allyshia

It'd be nice if the caller could just pass in things typed as one would expect, without having to jump …

mike_conleymike_conley

It'd be awesome to have the tool return an all-clear. Maybe even a ship-it, if the tool is configured for …

mike_conleymike_conley

} else {

mike_conleymike_conley

Spaces on either side of the =, space before the {

mike_conleymike_conley

Space after the if and before the {

mike_conleymike_conley

Spaces on either side of the +'s, and line up line 33 so that it's still contained (vertically) within the …

mike_conleymike_conley

2 Blank lines here.

SM smacleod

> 79 characters

SM smacleod

Can we break this out one per line, 4 space indented.

SM smacleod

I'm curious why you're having to call int(...) here. The values of self.settings *should* be the proper type, is this …

SM smacleod

Could we change the formatting here a bit (Also note the trailling comma): "load('%s'); runJSLint('%s', read('%s'), %s);" % ( self.runJSLint_path, …

SM smacleod
AL
AL
SM
  1. I just have a few comments, which hopefully answer some of your questions.
    
     - You're going to have to package the javascript files with Review Bot. This is done
       in setup.py, using 'package_data' and 'data_files' arguments to setup().
    
     - I think reading the file using the js engine is fine. If we were to try and pass a
       string, we'd have to worry about escaping things etc.
    
     - Maybe move the js files to something like 'ReviewBot/bot/reviewbot/lib/JSLint/*'
  2. bot/reviewbot/tools/jsLint.py (Diff revision 2)
     
     
    Show all issues
    This should probably be renamed. JSLint is more than a style checker.
  3. bot/reviewbot/tools/jsLint.py (Diff revision 2)
     
     
    Show all issues
    This should probably be adjusted like 'name'. It might be a good idea to include the version number of JSLint (Not the self.version, but the actual JSLint projects version) in the description here.
  4. bot/reviewbot/tools/jsLint.py (Diff revision 2)
     
     
    Show all issues
    This should probably be changed to be case insensitive. I've seen some use '.JS'
    
    I really would like to add better support for file matching to review bot in general, and make it so tool devs don't have to worry about this. I haven't thought of a good solution yet though.
  5. bot/reviewbot/tools/jsLint.py (Diff revision 2)
     
     
     
     
     
     
    Show all issues
    This appears to still be just copied from the pep8 tool, but you'll need to change this parsing code to match the output from your javscript.
  6. bot/reviewbot/tools/runJSLint.js (Diff revision 2)
     
     
     
     
     
     
    It's probably worth figuring out a way to pass these options in from the python code. If you can't pass arguments into the JS, you could probably generate 'options' in the python, and pass it as a string as part of the call to execute.
    
    Once you solve that problem, they can be added to the admin panel like the pep8 tool's 'max_line_length'.
  7. bot/setup.py (Diff revision 2)
     
     
     
    Show all issues
    let's put these in alphabetical order.
  8. 
      
AL
AL
AL
  1. 
      
  2. 
      
AA
  1. 
      
  2. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Import 'os' before the other 2 modules.
  3. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Add an extra line before the class declaration (should be a total of 2 blank lines)
  4. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
    1. I would actually suggest leaving the trailing commas.
      
      http://docs.python.org/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples
    2. Steven:
      
      Gotcha. I suggested removing them because I had to remove a couple on r/3434. Thanks!
  5. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  6. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  7. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  8. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  9. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  10. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  11. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  12. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  13. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  14. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  15. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  16. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  17. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  18. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  19. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    Remove trailing comma
  20. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
    I think this can be DRYed up a bit. Maybe we can do something like this:
    
    settings_keys = ['debug', 'devel', 'es5', 'evil', 'passfail', 'sloppy', 'white']
    jsLintOptions = dict((s, str(self.settings[s]).lower()) for s in settings_keys)
    jsLintOptions['maxlen'] = int(self.settings['maxlen'])
  21. 
      
SM
  1. 
      
  2. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
     
     
    Show all issues
    As Aamir pointed out, 'import os' should be coming first.
    
    Imports should be ordered like this:
    
    Standard Library imports (e.g. os, sys, etc.)
    ===BLANK LINE===
    Third Party imports
    ===BLANK LINE===
    Local Imports (e.g. 'reviewbot...')
    
    Also, imports should be ordered alphabetically within the three categories.
  3. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
     
     
    Show all issues
    If this is enabled, I think you should definitely indicate in
    the review body_top, or body_bottom that the scan was stopped after a
    single error. Maybe even print PASSED, or FAILED etc.
    
    In most cases we'll probably want to find all errors and print them,
    so I think you should probably default this to False. I think you were
    probably emulating the defaults from JSLint itself, but False seems
    more natural in our setting.
    
    Also, this reminds me, does JSLint stop on certain errors, and refuse to
    continue scanning until they are fixed? If this is the case, is there a
    way we can detect this, and indicate to the user that scanning stopped
    after that point?
    1. JSLint will stop after a certain threshold and refuses to continue scanning. (This is why some people prefer JSHint). At that point, there is an inline comment that indicates that scanning was stopped. Ideally (and it appears consistently in my trials on jslint.com) that the cut off would end up being around the 70-80% range of scanning on average. The passfail setting being false should ensure that the most scanning possible will happen. I will default to false as you suggest. For the cutoff point though, is an inline comment enough to indicate to the user that scanning stopped?
  4. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    Show all issues
    This should probably be done only once per request, instead of every
    time handle_file is called (Once per file).
    
    You'll probably want to override handle_files, or execute, and take
    care of this stuff there. Make sure to call the base class' method
    from your overridden method.
  5. bot/reviewbot/tools/jsLint.py (Diff revision 3)
     
     
     
    What exactly does JSLint output which causes the failure? I'm
    wondering if it's something that should be displayed in the review
    another way?
    1. Actually I am not sure this except is still justified. A while back I was having runJSLint.js output "no errors" if no errors were found, so at this point in jsLint.py, there would be no correctly formatted line/col numbers. JSLint shouldnt really output anything that isnt a line/col/message, but I'd have to verify. Is it worth leaving the except for safety ?
  6. bot/reviewbot/tools/jslint.js (Diff revision 3)
     
     
    Quite the opinionated option, with quite a useless description.
    
    Not sure what this does, but it gives me the vibe it could be a hot
    button issue. Do you think this is an option we should include? I'd
    say we should at least figure out what it does.
    1. Yeah it really is vague. I would be interested to see what this option does, and I don't think we should offer up this option to RB users in the options panel (at least not by this name) unless it really proves to do something useful.
    2. Just to follow up, this option finds uses of blocking "Sync" methods in Node.Js code. Enabling the "tolerate stupid" option allows these instances of "Sync" methods, which generally would be a matter of taste / design decisions. So I don't think the RB users should necessarily see this option in their panel, and we should leave the JSLint default at true, since forbidding Sync can be controlled by human discretion.
  7. bot/setup.py (Diff revision 3)
     
     
    Show all issues
    I don't think this is quite correct. The package key should probably be
    'reviewbot', or 'reviewbot.tools'.
    
    I'm fine with lib being inside the tools folder, but I think the JSLint
    specific stuff needs to be put in a sub folder as well (We may end up
    expanding with other things in here, and I'd like to have it organized).
    
    So, you'll have something like:
    
    package_data={
        'reviewbot.tools': [
            'lib/jslint/*.js',
        ]
    }
    1. Sounds good. Will do that.
  8. bot/setup.py (Diff revision 3)
     
     
    Show all issues
    Let's rename jsLint.py to jslint.py.
    
    So, this should be changed to 'jslint = reviewbot.tools.jslint:JSLintTool',
  9. 
      
AL
AL
  1. 
      
  2. 
      
AL
  1. Somme comments to guide reviewers and sollicit feedback.
  2. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Might not be necessary to sent the lowercase form of the strings. runJSLint is checking the content of the string anyway, it could check for upper case "True" and "False".
  3. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    self.jsLintOptions is a dict. wondering if we should depend on auto-formatting of dict to string used here or whether we should use an explicit json.dumps() type conversion to string. Thoughts on why that might be advantageous?
    1. See my comments on Mike's review. We want to use json.dumps()
  4. Show all issues
    See comment on line 127 of jslint.py. Originally this check did not exist, and we passed the straight string value to jslint as an option. Since conversion to boolean value, kept on accepting lower case string since it best mimics the js boolean values (you could argue in the interest of modularity, if another program were to use this js script, they might not send in uppercase 'True' and 'False', so we are picking a standard).
    1. This will be fixed by other changes, see comments on Mike's review.
  5. 
      
TA
  1. Just a few comments from reading over the code
  2. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Should this be called whitespace instead?
  3. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Maybe you could add a comment about which file caused the failure?
  4. 
      
mike_conley
  1. 
      
  2. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
     
    Show all issues
    I believe, generally, we prefer the higher level imports to go above the more granular ones, so line 3 and 4 should be swapped.
    1. Oops - I also copied this from pep8.py -- should both be changed?
    2. It should be changed here, but don't worry about for pep8. There
      are a number of style problems with the pep8 tool, and I'm going
      to fix them along with some other modifications I'm making to it.
  3. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Evil? Or Eval? Or Evil-Eval?
    
    Not sure if this is being cheeky, or just a typo. :)
    1. I should probably state that these variable names were borrowed straight from JSLINT's own nomenclature so as to establish a relationship between our options and the JSLINT options. If this is not a good idea, please let me know.
    2. I would say keep the relationship with the JSLint options.
  4. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Isn't 80 the traditional character limit? Or is something here 0-indexed? Or have I just been lied to all my life?
    1. I actually borrowed this from our PEP8 tool's default. Should that be 80...?
    2. The preferred maximum line length for Python, according to PEP8, is 79
      characters: http://www.python.org/dev/peps/pep-0008/#maximum-line-length
      
      As for the JSLint default here, Crockford says, "Avoid lines longer than 80
      characters". So we should probably bump this to 80.
  5. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    PACKAGE_ROOT is in all caps, and lib_path is not. Why?
  6. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Can you not simply pass the actual True and False literals to the script? Will the JS engine accept something like:
    
    js -e "load('foo'); runJSLint('bar', read('baz'), {someOption: false, otherOption: true}"
    
    ?
    1. Since all of the arguments passed in to the jslint.js script via the spidermonkey command line are done here by string value, True ends up as 'True', not as its boolean equivalent. Hence the need to translate at some point between the uppercase-led string and the boolean in js. (which speaks to the reviewer's comments on line 11 of runJSLint.js). Is this acceptable?
    2. So, the problem is stemming from the fact that we're passing the options
      dictionary as a string like this:
      
      148  "load('%s'); runJSLint('%s', read('%s'), %s);"
      149      % (self.runJSLint_path, self.jsLint_path, path,	
      150         self.jsLintOptions)
      
      If you take this dictionary, with the python True & False, and run it
      through 'json.dumps()', you should be able to pass it in, and have
      proper JS true and false.
  7. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Yeah, maybe this is overkill then.
  8. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    This line will need to be indented two more spaces, since it's still inside the dicts parens.
  9. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Is there a reason this is not called *before* we do all of JSLineTool's specialized stuff?
    1. If I call super(...) first, that will fire off the call to handle_file(...) (handle individual file) for each file in the review request (also overridden in JSLintTool). The variable initialization that happens in the beginning of the handle_files(...) method overridden in JSLintTool must necessarily occur before that happens.
  10. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    Let's put a newline after the if-block.
  11. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
    Show all issues
    I don't think the string representation of dict for the things you're passing is going to change anytime soon, so I'm not too worried, personally.
    
    This line needs to have two spaces removed from its indentation.
  12. bot/reviewbot/tools/jslint.py (Diff revision 4)
     
     
     
     
     
     
    Show all issues
    Yeah, a little less silent-fail would probably be good here.
    1. A couple of options here:
      1- return False and count this file as ignored rather than processed. The list of ignored and processed files are displayed to the user in the body_top of the review.
      2- explicitly tell the user that an error occurred while processing the file, i.e. in an ERROR section of the review. No support for this yet.
      
      In general this use case should occur rarely.
      
      Thoughts on this would be appreciated.
    2. Since we don't have the proper support for errors, just add to the ignored list
      for now. Please put a TODO comment indicating we should change this to properly
      display an error message.
  13. Show all issues
    This function should be documented.
    
    Also, as a general rule, we tend to use 4-space indentation for Javascript.
  14. Show all issues
    var options
  15. Show all issues
    Should be formatted like:
    
    if (custom_options) {
      ...
    }
  16. Show all issues
    for (var op in custom_options) {
  17. Show all issues
    var custom
  18. Show all issues
    It'd be nice if the caller could just pass in things typed as one would expect, without having to jump through type conversion hoops...see my question above about passing boolean literals.
    1. As mentioned above, passing the dictionary in JSON encoded should fix this.
  19. bot/reviewbot/tools/lib/jslint/runJSLint.js (Diff revision 4)
     
     
     
     
    Show all issues
    It'd be awesome to have the tool return an all-clear. Maybe even a ship-it, if the tool is configured for it. (The ship-it thing might be out of scope, and a nice-to-have...but I think hearing about a no-error run would be a good thing).
    1. Review Bot already contains support for Ship-It on no issues. Right now
      no errors is just a review telling you the processed files and having no
      comments, but a nice message could be useful.
  20. bot/reviewbot/tools/lib/jslint/runJSLint.js (Diff revision 4)
     
     
     
    Show all issues
    } else {
  21. Show all issues
    Spaces on either side of the =, space before the {
  22. Show all issues
    Space after the if and before the {
  23. bot/reviewbot/tools/lib/jslint/runJSLint.js (Diff revision 4)
     
     
     
    Show all issues
    Spaces on either side of the +'s, and line up line 33 so that it's still contained (vertically) within the parentheses of the above line.
  24. 
      
AL
AL
AL
SM
  1. The latest diff seems to have been uploaded strangely, only showing part of the change. Could you please update this again with the full patch?
  2. 
      
AL
SM
  1. Allyshia, this is looking really good. I pulled the patch down, and
    ran Review Bot locally against it with PEP8 and JSLint :D
    
    We are going to have to update this to be a separate package before
    it can be committed though (The JSLint Licensing issues :/). We can
    discuss that in IRC.
  2. bot/reviewbot/tools/jslint.py (Diff revision 6)
     
     
     
     
    Show all issues
    2 Blank lines here.
  3. bot/reviewbot/tools/jslint.py (Diff revision 6)
     
     
    Show all issues
    > 79 characters
  4. bot/reviewbot/tools/jslint.py (Diff revision 6)
     
     
     
    Show all issues
    Can we break this out one per line, 4 space indented.
  5. bot/reviewbot/tools/jslint.py (Diff revision 6)
     
     
    Show all issues
    I'm curious why you're having to call int(...) here. The values of self.settings *should* be the proper type, is this not the case?
  6. bot/reviewbot/tools/jslint.py (Diff revision 6)
     
     
     
     
    Show all issues
    Could we change the formatting here a bit (Also note the trailling comma):
    
    "load('%s'); runJSLint('%s', read('%s'), %s);" % (
        self.runJSLint_path,
        self.jsLint_path,
        path,
        json.dumps(self.jsLintOptions)),
  7. 
      
AL
mike_conley
  1. Since this is being continued in a different review request, can we mark this as discarded?
  2. 
      
AL
Review request changed
Status:
Discarded