~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/scripts/utilities/build/jslint.py

Brought back the js utilities that I somehow killed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""jslint.py - run the JSLint linter ."""
 
2
 
 
3
__metaclass__ = type
 
4
__all__ = []
 
5
 
 
6
import optparse
 
7
import os
 
8
import subprocess
 
9
import sys
 
10
 
 
11
from bzrlib import branch, errors, workingtree
 
12
from bzrlib.plugin import load_plugins
 
13
 
 
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')
 
17
 
 
18
class FiletypeFilter:
 
19
    include_html = False
 
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()
 
25
 
 
26
 
 
27
class FileFinder:
 
28
    def __init__(self):
 
29
        self.tree = workingtree.WorkingTree.open_containing('.')[0]
 
30
 
 
31
    def find_files_to_lint(self, delta):
 
32
        """Return the modified and added files in a tree from a delta."""
 
33
        files_to_lint = []
 
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])
 
38
 
 
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)]
 
41
 
 
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()
 
47
        else:
 
48
            return self.find_files_to_lint(working_tree_delta)
 
49
 
 
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)
 
54
 
 
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()
 
58
        if submit is None:
 
59
            submit = self.tree.branch.get_parent()
 
60
            if submit is None:
 
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))
 
64
 
 
65
    def find_all_files_to_lint(self):
 
66
        """Return all the JS files that can be linted."""
 
67
        all_files = []
 
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'):
 
72
                continue
 
73
            if js_filter(path):
 
74
                all_files.append(self.tree.abspath(path))
 
75
        return all_files
 
76
 
 
77
 
 
78
class JSLinter:
 
79
    """Linter for Javascript."""
 
80
 
 
81
    def __init__(self, options=None):
 
82
        self.options = options
 
83
 
 
84
    def jslint_rhino(self, filenames):
 
85
        """Run the linter on all selected files using rhino."""
 
86
        args = ['rhino', '-f', FULLJSLINT, JSLINT_WRAPPER]
 
87
        if self.options:
 
88
            args.extend(['-o', self.options])
 
89
        args.extend(filenames)
 
90
        jslint = subprocess.Popen(args)
 
91
        return jslint.wait()
 
92
 
 
93
    def jslint_spidermonkey(self, filenames):
 
94
        """Run the linter on all selected files using spidermonkey."""
 
95
        args = ['js', '-f', FULLJSLINT, JSLINT_WRAPPER]
 
96
        if self.options:
 
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)
 
103
        if self.options:
 
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())
 
108
            fh.close()
 
109
            jslint.stdin.write('\nEOF\n')
 
110
 
 
111
        return jslint.wait()
 
112
 
 
113
 
 
114
def get_options():
 
115
    """Parse the command line options."""
 
116
    parser = optparse.OptionParser(
 
117
        usage="%prog [options] [files]",
 
118
        description=(
 
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."
 
123
            ))
 
124
    parser.add_option(
 
125
        '-o', '--options', dest='options',
 
126
        help=('JS file returning a configuration object for the linter.'))
 
127
    parser.add_option(
 
128
        '-a', '--all', dest='all', default=False,
 
129
        action='store_true',
 
130
        help=('Lint all JavaScript files in the branch.'))
 
131
    parser.add_option(
 
132
        '-p', '--parent', dest='parent', default=False,
 
133
        action='store_true',
 
134
        help=('Lint all JavaScript files modified from the submit: branch.'))
 
135
    parser.add_option(
 
136
        '-w', '--working-tree', dest='working_tree', default=False,
 
137
        action='store_true',
 
138
        help=('Only lint changed files in the working tree.'))
 
139
    parser.add_option(
 
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'))
 
143
    parser.add_option(
 
144
        '-i', '--include-html', dest='html', default=False,
 
145
        action='store_true', help=('Also lint .html files.'))
 
146
 
 
147
    options, args = parser.parse_args()
 
148
    if len(args) > 0:
 
149
        if options.all or options.parent or options.working_tree:
 
150
            parser.error(
 
151
                'Cannot specify files with --all, --parent or --working-tree')
 
152
    else:
 
153
        count = 0
 
154
        if options.all:
 
155
            count += 1
 
156
        if options.parent:
 
157
            count += 1
 
158
        if options.working_tree:
 
159
            count += 1
 
160
        if count > 1:
 
161
            parser.error(
 
162
                'Only one of --all, --parent or --working-tree should be '
 
163
                'specified.')
 
164
    if options.engine not in ['js', 'rhino']:
 
165
        parser.error(
 
166
            'Unrecognized engine. Use either "js" or "rhino".')
 
167
    return options, args
 
168
 
 
169
 
 
170
def main():
 
171
    options, args = get_options()
 
172
    linter = JSLinter(options.options)
 
173
    js_filter.include_html = options.html
 
174
    if args:
 
175
        files = [f for f in args if js_filter(f)]
 
176
    else:
 
177
        load_plugins()
 
178
        finder = FileFinder()
 
179
        if options.all:
 
180
            files = finder.find_all_files_to_lint()
 
181
        elif options.working_tree:
 
182
            files = finder.find_files_to_lint_from_working_tree()
 
183
        elif options.parent:
 
184
            files = finder.find_files_to_lint_from_parent()
 
185
        else:
 
186
            files = finder.find_files_to_lint_from_working_tree_or_parent()
 
187
    if not files:
 
188
        print 'jslint: No files to lint.'
 
189
    else:
 
190
        if len(files) == 1:
 
191
            print 'jslint: 1 file to lint.'
 
192
        else:
 
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
 
198
        else:
 
199
            raise AssertionError('Unknown engine: %s' % options.engine)
 
200
        sys.exit(jslint(files))
 
201