diff --git a/tools/find-add-class b/tools/find-add-class new file mode 100755 index 0000000000..6903de753e --- /dev/null +++ b/tools/find-add-class @@ -0,0 +1,47 @@ +#!/usr/bin/env python +from __future__ import absolute_import +from __future__ import print_function + +from lib.find_add_class import display, find +import glob +import optparse +import sys + +from six.moves import filter +try: + import lister + from typing import cast, Callable, Dict, Iterable, List +except ImportError as e: + print("ImportError: {}".format(e)) + print("You need to run the Zulip linters inside a Zulip dev environment.") + print("If you are using Vagrant, you can `vagrant ssh` to enter the Vagrant guest.") + sys.exit(1) + +def process_files(): + # type: () -> None + + usage = ''' + Use this tool to find HTML classes that we use in our JS code. + This looks for calls to addClass, and if you use the -v option, + you will get a display of (fn, html_class) tuples that + represent addClass calls. + + If you call it with no options, the tool acts as a linter, and + it will complain if it can't resolve the class for an addClass() + call. + ''' + + parser = optparse.OptionParser(usage=usage) + parser.add_option('--verbose', '-v', + action='store_true', default=False, + help='Show where calls are.') + (options, _) = parser.parse_args() + + fns = glob.glob('static/js/*.js') + if options.verbose: + display(fns) + else: + find(fns) + +if __name__ == '__main__': + process_files() diff --git a/tools/lib/find_add_class.py b/tools/lib/find_add_class.py new file mode 100644 index 0000000000..7f2b0c6140 --- /dev/null +++ b/tools/lib/find_add_class.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import +from __future__ import print_function + +from typing import Set, Tuple + +import os +import re + +GENERIC_KEYWORDS = [ + 'active', + 'alert', + 'danger', + 'condensed', + 'disabled', + 'error', + 'expanded', + 'hide', + 'notdisplayed', + 'popover', + 'success', + 'text-error', + 'warning', +] + +def raise_error(fn, i, line): + # type: (str, int, str) -> None + error = ''' + In %s line %d there is the following line of code: + + %s + + Our tools want to be able to identify which modules + add which HTML/CSS classes, and we need two things to + happen: + + - The code must explicitly name the class. + - Only one module can refer to that class (unless + it is something generic like an alert class). + + If you get this error, you can usually address it by + refactoring your code to be more explicit, or you can + move the common code that sets the class to a library + module. If neither of those applies, you need to + modify %s + ''' % (fn, i, line, __file__) + raise Exception(error) + +def generic(html_class): + # type: (str) -> bool + for kw in GENERIC_KEYWORDS: + if kw in html_class: + return True + +def display(fns): + # type: (List[str]) -> None + for tup in find(fns): + # this format is for code generation purposes + print(' ' * 8 + repr(tup) + ',') + +def find(fns): + # type: (List[str]) -> List[Tuple[str, str]] + encountered = set() # type: Set[str] + tups = [] # type: List[Tuple[str, str]] + for fn in fns: + lines = list(open(fn)) + fn = os.path.basename(fn) + module_classes = set() # type: Set[str] + for i, line in enumerate(lines): + if 'addClass' in line: + html_classes = [] # type: List[str] + m = re.search('addClass\([\'"](.*?)[\'"]', line) + if m: + html_classes = [m.group(1)] + if not html_classes: + if 'bar-success' in line: + html_classes = ['bar-success', 'bar-danger'] + elif 'color_class' in line: + continue + elif 'stream_dark' in line: + continue + elif fn == 'signup.js' and 'class_to_add' in line: + html_classes = ['error', 'success'] + elif fn == 'ui.js' and 'status_classes' in line: + html_classes = ['alert'] + + if not html_classes: + raise_error(fn, i, line) + for html_class in html_classes: + if generic(html_class): + continue + if html_class in module_classes: + continue + if html_class in encountered: + raise_error(fn, i, line) + tups.append((fn, html_class)) + module_classes.add(html_class) + encountered.add(html_class) + return tups diff --git a/tools/lint-all b/tools/lint-all index 1563fb776e..a74f9f15b3 100755 --- a/tools/lint-all +++ b/tools/lint-all @@ -401,6 +401,12 @@ try: result = subprocess.call(args) return result + @lint + def add_class(): + # type: () -> int + result = subprocess.call(['tools/find-add-class']) + return result + @lint def css(): # type: () -> int