1
"""jslint.py - run the JSLint linter ."""
11
from bzrlib import branch, errors, workingtree
12
from bzrlib.plugin import load_plugins
14
HERE = os.path.join(os.path.dirname(__file__))
15
FULLJSLINT = os.path.join(HERE, 'fulljslint.js')
16
JSLINT_WRAPPER = os.path.join(HERE, 'jslint-wrapper.js')
20
def __call__(self, path):
21
"""Return True for filetypes we want to lint."""
22
return path.endswith('.js') or (self.include_html and
23
path.endswith('.html'))
24
js_filter = FiletypeFilter()
29
self.tree = workingtree.WorkingTree.open_containing('.')[0]
31
def find_files_to_lint(self, delta):
32
"""Return the modified and added files in a tree from a delta."""
34
files_to_lint.extend(info[0] for info in delta.added)
35
files_to_lint.extend(info[0] for info in delta.modified)
36
# And look also at the renamed attribute for modified files.
37
files_to_lint.extend(info[0] for info in delta.renamed if info[4])
39
# Select only the appropriate files and turn them in absolute paths.
40
return [self.tree.abspath(f) for f in files_to_lint if js_filter(f)]
42
def find_files_to_lint_from_working_tree_or_parent(self):
43
"""Return the file paths to lint based on working tree changes."""
44
working_tree_delta = self.tree.changes_from(self.tree.basis_tree())
45
if not working_tree_delta.has_changed():
46
return self.find_files_to_lint_from_parent()
48
return self.find_files_to_lint(working_tree_delta)
50
def find_files_to_lint_from_working_tree(self):
51
"""Return the file path to lint based on working tree changes."""
52
working_tree_delta = self.tree.changes_from(self.tree.basis_tree())
53
return self.find_files_to_lint(working_tree_delta)
55
def find_files_to_lint_from_parent(self):
56
"""Return the file path to lint based on working tree changes."""
57
submit = self.tree.branch.get_submit_branch()
59
submit = self.tree.branch.get_parent()
61
raise errors.NoSubmitBranch(self.tree.branch)
62
submit_tree = branch.Branch.open(submit).basis_tree()
63
return self.find_files_to_lint(self.tree.changes_from(submit_tree))
65
def find_all_files_to_lint(self):
66
"""Return all the JS files that can be linted."""
68
for file_id in self.tree:
69
path = self.tree.id2path(file_id)
70
# Skip build files and third party files.
71
if path.startswith('lib') or path.startswith('build'):
74
all_files.append(self.tree.abspath(path))
79
"""Linter for Javascript."""
81
def __init__(self, options=None):
82
self.options = options
84
def jslint_rhino(self, filenames):
85
"""Run the linter on all selected files using rhino."""
86
args = ['rhino', '-f', FULLJSLINT, JSLINT_WRAPPER]
88
args.extend(['-o', self.options])
89
args.extend(filenames)
90
jslint = subprocess.Popen(args)
93
def jslint_spidermonkey(self, filenames):
94
"""Run the linter on all selected files using spidermonkey."""
95
args = ['js', '-f', FULLJSLINT, JSLINT_WRAPPER]
97
args.extend(['-o', self.options])
98
args.extend(filenames)
99
jslint = subprocess.Popen(args, stdin=subprocess.PIPE)
100
# SpiderMonkey can only read from stdin, so we are multiplexing the
101
# different files on stdin.
102
files_to_send = list(filenames)
104
files_to_send.insert(0, self.options)
105
for filename in files_to_send:
106
fh = open(filename, 'r')
107
jslint.stdin.write(fh.read())
109
jslint.stdin.write('\nEOF\n')
115
"""Parse the command line options."""
116
parser = optparse.OptionParser(
117
usage="%prog [options] [files]",
119
"Run Douglas Crockford JSLint script on the JS files. "
120
"By default, all modified files in the current working tree are "
121
"linted. Or all modified files since the parent branch, if there "
122
"are no changes in the current working tree."
125
'-o', '--options', dest='options',
126
help=('JS file returning a configuration object for the linter.'))
128
'-a', '--all', dest='all', default=False,
130
help=('Lint all JavaScript files in the branch.'))
132
'-p', '--parent', dest='parent', default=False,
134
help=('Lint all JavaScript files modified from the submit: branch.'))
136
'-w', '--working-tree', dest='working_tree', default=False,
138
help=('Only lint changed files in the working tree.'))
140
'-e', '--engine', dest='engine', default='js', action='store',
141
help=('Javascript engine to use. Defaults to "js" (SpiderMonkey). '
142
'Use "rhino" to use the Java-based Rhino engine'))
144
'-i', '--include-html', dest='html', default=False,
145
action='store_true', help=('Also lint .html files.'))
147
options, args = parser.parse_args()
149
if options.all or options.parent or options.working_tree:
151
'Cannot specify files with --all, --parent or --working-tree')
158
if options.working_tree:
162
'Only one of --all, --parent or --working-tree should be '
164
if options.engine not in ['js', 'rhino']:
166
'Unrecognized engine. Use either "js" or "rhino".')
171
options, args = get_options()
172
linter = JSLinter(options.options)
173
js_filter.include_html = options.html
175
files = [f for f in args if js_filter(f)]
178
finder = FileFinder()
180
files = finder.find_all_files_to_lint()
181
elif options.working_tree:
182
files = finder.find_files_to_lint_from_working_tree()
184
files = finder.find_files_to_lint_from_parent()
186
files = finder.find_files_to_lint_from_working_tree_or_parent()
188
print 'jslint: No files to lint.'
191
print 'jslint: 1 file to lint.'
193
print 'jslint: %d files to lint.' % len(files)
194
if options.engine == 'js':
195
jslint = linter.jslint_spidermonkey
196
elif options.engine == 'rhino':
197
jslint = linter.jslint_rhino
199
raise AssertionError('Unknown engine: %s' % options.engine)
200
sys.exit(jslint(files))