1. Timothy Corbett-Clark
  2. sublime-lint-free

Commits

Timothy Corbett-Clark  committed 38f9d2e Draft

added another lint example and generalised the regex grouping

  • Participants
  • Parent commits f7ea618
  • Branches default

Comments (0)

Files changed (6)

File README.md

View file
 
         // Delay in seconds after which annotator commands are run following the
         // last load, save, or edit.
-        "throttle_delay": 500,
+        "throttle_delay": 1000,
 
         // Maximum duration in seconds for an annotator command to run before it is
         // terminated because it is considered as taking too long.
-        "timeout_after": 1000,
+        "timeout_after": 500,
 
         // Definition of annotators as a list of dictionaries. Each dictionary must
         // contain the following keys:
         //  name - a string to describe the annotator
         //  syntax - only run if this syntax name is in sublime's syntax string
         //  command - OS command with %s for filename substitution.
-        //  output_regex - decode the output from the command:
+        //  output_regex - decode the output from the command. In the simple case:
         //      the first regex group provides the line number
         //      the second regex group provides the annotation message
-        "annotators" : [
+        //
+        // The output_regex can contain more than 2 groups, in which case the
+        // code attempts a match by trying each pair of regex groups until the
+        // first plausible match is encounted. For example, if the values of the
+        // regex groups are: (a, b, c, d) then first (a, b) is tested for a
+        // plausible match against (line_number, message), then (b, c) is
+        // tested, and finally (c, d) is tested. Plausible means both values are
+        // non-None and the first can be cast to an integer. This is clearly a
+        // degenerate form of the above special case with only two values. The
+        // advantage is that multiple alternative matches can be used (which
+        // precludes named groups), and the order of the line number and the
+        // message can be arbitrary (e.g. by including a fixed non-numeric match
+        // in the first group).
+        "annotators": [
+            {
+                "name": "coffeelint",
+                "command": "coffeelint --csv '%s'",
+                "syntax": "CoffeeScript",
+                "output_regex": "^(.*,(\\d+),.*,(.*))|(.*Parse error on line (\\d+):(.*))$"
+            },
+            {
+                "name": "todo",
+                "command": "grep '%s' -e TODO -n",
+                "syntax": "",
+                "output_regex": "^(\\d+):(.*)$"
+            },
             {
                 "name": "pep8",
+                "command": "pep8 '%s' --max-line-length=80",
                 "syntax": "Python",
-                "command": "pep8 '%s' --max-line-length=80",
-                "output_regex": "^.*\\:(\\d*)\\:\\d*\\:(.*)$"
+                "output_regex": "^.*:(\\d+):\\d*:(.*)$"
             },
             {
                 "name": "pyflake",
+                "command": "pyflakes '%s'",
                 "syntax": "Python",
-                "command": "pyflakes '%s'",
-                "output_regex": "^.*\\:(\\d*)\\:(.*)$"
+                "output_regex": "^.*:(\\d+):(.*)$"
             },
             {
                 "name": "mccabe",
+                "command": "flake8 --max-complexity=8 '%s' | grep -e 'is too complex'",
                 "syntax": "Python",
-                "command": "flake8 --max-complexity=8 '%s' | grep -e 'is too complex'",
-                "output_regex": "^.*\\:(\\d*)\\:\\d*\\:(.*)$"
+                "output_regex": "^.*:(\\d+):\\d*:(.*)$"
             },
             {
                 "name": "ruby",
+                "command": "ruby -wc '%s'",
                 "syntax": "Ruby",
-                "command": "ruby -cw '%s'",
-                "output_regex": "^.*\\:(\\d*)\\:(.*)$"
+                "output_regex": "^.*:(\\d+):(.*)$"
             }
         ]
     }

File config.py

View file
         return syntax and self.syntax in syntax
 
     def parse_line_of_output(self, line):
-        """Return (line_number, message) or raise ValueError if no match."""
+        """Return (line_number, message) or raise ValueError if no match.
+
+        Attempts a match by trying each pair of regex groups until a plausible
+        match is encounted. For example, if the values of the regex groups are
+        (a, b, c, d), then first (a, b) is tested for a plausible match against
+        (line_number, message), then (b, c) is tested, and finally (c, d) is
+        tested. Plausible means both values are non-None and the first can be
+        cast to an integer.
+
+        """
         match = self.output_regex.match(line.strip())
         if match is not None:
-            line_number, message = match.groups()
-            return int(line_number), message.strip()
+            matches = match.groups()
+            i = 0
+            while i < len(matches) - 1:
+                print i
+                try:
+                    line_number, message = matches[i:i + 2]
+                    if line_number is not None and message is not None:
+                        return int(line_number), message.strip()
+                except:
+                    pass
+                i += 1
         raise ValueError('No match')
 
 

File example.py

-# This is a file to test the whole plugin with Python linters.
-
-import os, sys  # multiple imports, and unused sys
-import re
-import binascii
-
-os.path
-
-def f(**kwargs):  # 2 blank lines between top level functions
-    unused_variable = 'hello'  # unused variable
-
-f(arg1= 3)  # bad spacing around keyword arguments
-
-
-# Some comment which is longer than the recommended maximum of 80 characters, ah we need some more...
-
-
-def a_complex_function():  # mccabe complexity measure declares this too complex
-    # Taken from standard library base64.b32decode.
-    # Define some variables to reduce noise.
-    EMPTYSTRING = _b32rev = _translate = casefold = map01 = s = None
-    quanta, leftover = divmod(len(s), 8)
-    if leftover:
-        raise TypeError('Incorrect padding')
-    # Handle section 2.4 zero and one mapping.  The flag map01 will be either
-    # False, or the character to map the digit 1 (one) to.  It should be
-    # either L (el) or I (eye).
-    if map01:
-        s = _translate(s, {'0': 'O', '1': map01})
-    if casefold:
-        s = s.upper()
-    # Strip off pad characters from the right.  We need to count the pad
-    # characters because this will tell us how many null bytes to remove from
-    # the end of the decoded string.
-    padchars = 0
-    mo = re.search('(?P<pad>[=]*)$', s)
-    if mo:
-        padchars = len(mo.group('pad'))
-        if padchars > 0:
-            s = s[:-padchars]
-    # Now decode the full quanta
-    parts = []
-    acc = 0
-    shift = 35
-    for c in s:
-        val = _b32rev.get(c)
-        if val is None:
-            raise TypeError('Non-base32 digit found')
-        acc += _b32rev[c] << shift
-        shift -= 5
-        if shift < 0:
-            parts.append(binascii.unhexlify('%010x' % acc))
-            acc = 0
-            shift = 35
-    # Process the last, partial quanta
-    last = binascii.unhexlify('%010x' % acc)
-    if padchars == 0:
-    # TODO hi
-        last = ''                       # No characters
-    elif padchars == 1:
-        last = last[:-1]
-    elif padchars == 3:
-        last = last[:-2]
-    elif padchars == 4:
-        last = last[:-3]
-    elif padchars == 6:
-        last = last[:-4]
-    else:
-        raise TypeError('Incorrect padding')
-    parts.append(last)
-    return EMPTYSTRING.join(parts)
-
-# blank line at end of file
-

File sublime-lint-free.sublime-settings

View file
 
     // Maximum duration in milliseconds for an annotator command to run before
     // it is terminated because it is taking too long.
-    "timeout_after": 1000,
+    "timeout_after": 500,
 
-    // Definition of annotators as a list of dictionaries. Each dictionary must
-    // contain the following keys:
-    //  name - a string to describe the annotator.
-    //  syntax - only run if this syntax name is in sublime's syntax string
-    //  command - OS command with %s for filename substitution.
-    //  output_regex - decode the output from the command:
+    // The definition of the annotators is a list of dictionaries where each
+    // dictionary must contain the following keys:
+    //    name - a string to describe the annotator.
+    //    syntax - only run if this syntax name is in sublime's syntax string
+    //    command - OS command with %s for filename substitution.
+    //    output_regex - decode the output from the command. In the simple case:
     //      the first regex group provides the line number
     //      the second regex group provides the annotation message
     //
+    // The output_regex can contain more than 2 groups, in which case the code
+    // attempts a match by trying each pair of regex groups until the first
+    // plausible match is encounted. For example, if the values of the regex
+    // groups are: (a, b, c, d) then first (a, b) is tested for a plausible
+    // match against (line_number, message), then (b, c) is tested, and finally
+    // (c, d) is tested. Plausible means both values are non-None and the first
+    // can be cast to an integer. This is clearly a degenerate form of the above
+    // special case with only two values. The advantage is that multiple
+    // alternative matches can be used (which precludes named groups), and the
+    // order of the line number and the message can be arbitrary (e.g. by
+    // including a fixed non-numeric match in the first group).
+    //
     // For example
     //     "annotators" : [
     //     {
     //         "name": "todo",
     //         "command": "grep '%s' -e TODO -n",
     //         "syntax": "",
-    //         "output_regex": "^(\\d*)\\:(.*)$"
+    //         "output_regex": "^(\\d*):(.*)$"
     //     },
     //     {
     //         "name": "pep8",
     //         "syntax": "Python",
     //         "command": "pep8 '%s' --max-line-length=80",
-    //         "output_regex": "^.*\\:(\\d*)\\:\\d*\\:(.*)$"
+    //         "output_regex": "^.*:(\\d*):\\d*:(.*)$"
     //     },
     //     {
     //         "name": "pyflake",
     //         "syntax": "Python",
     //         "command": "pyflakes '%s'",
-    //         "output_regex": "^.*\\:(\\d*)\\:(.*)$"
+    //         "output_regex": "^.*:(\\d*):(.*)$"
     //     },
     //     {
     //         "name": "mccabe",  // available in flake8
     //         "syntax": "Python",
     //         "command": "flake8 --max-complexity=8 '%s' | grep -e 'is too complex'",
-    //         "output_regex": "^.*\\:(\\d*)\\:\\d*\\:(.*)$"
+    //         "output_regex": "^.*:(\\d*):\\d*:(.*)$"
     //     },
     //     {
     //         "name": "ruby",
     //         "syntax": "Ruby",
     //         "command": "ruby -wc '%s'",
-    //         "output_regex": "^.*\\:(\\d*)\\:(.*)$"
+    //         "output_regex": "^.*:(\\d*):(.*)$"
+    //     },
+    //     {
+    //         "name": "coffeelint",
+    //         "command": "coffeelint --csv '%s'",
+    //         "syntax": "CoffeeScript",
+    //         "output_regex": "^(.*,(\\d+),.*,(.*))|(.*Parse error on line (\\d+):(.*))$"
     //     }
     // ]
     "annotators": []

File test_files/coffeescript.coffee

View file
+twoSpaces = () ->
+  fourSpaces = () ->
+      eightSpaces = () -> # mixed 2 and 4 space indentation
+            'this is valid CoffeeScript' # mixed 2 and 8 space indentation
+
+# Some comment which is longer than the recommended maximum of 80 characters, ah we need some more...
+
+class thisShouldBeCamelCase # class names should be CamelCase
+  x = 3
+
+alert('needless trailing semicolon');
+
+myFunction a, b, 1:2, 3:4 # no implicit braces (if configuration enabled
+
+throw "i made a boo boo" # don't throw strings
+
+no_backticks = `1+2`
+

File test_files/python.py

View file
+# This is a file to test the whole plugin with Python linters.
+
+import os, sys  # multiple imports, and unused sys
+import re
+import binascii
+
+os.path
+
+def f(**kwargs):  # 2 blank lines between top level functions
+    unused_variable = 'hello'  # unused variable
+
+f(arg1= 3)  # bad spacing around keyword arguments
+
+
+# Some comment which is longer than the recommended maximum of 80 characters, ah we need some more...
+
+
+def a_complex_function():  # mccabe complexity measure declares this too complex
+    # Taken from standard library base64.b32decode.
+    # Define some variables to reduce noise.
+    EMPTYSTRING = _b32rev = _translate = casefold = map01 = s = None
+    quanta, leftover = divmod(len(s), 8)
+    if leftover:
+        raise TypeError('Incorrect padding')
+    # Handle section 2.4 zero and one mapping.  The flag map01 will be either
+    # False, or the character to map the digit 1 (one) to.  It should be
+    # either L (el) or I (eye).
+    if map01:
+        s = _translate(s, {'0': 'O', '1': map01})
+    if casefold:
+        s = s.upper()
+    # Strip off pad characters from the right.  We need to count the pad
+    # characters because this will tell us how many null bytes to remove from
+    # the end of the decoded string.
+    padchars = 0
+    mo = re.search('(?P<pad>[=]*)$', s)
+    if mo:
+        padchars = len(mo.group('pad'))
+        if padchars > 0:
+            s = s[:-padchars]
+    # Now decode the full quanta
+    parts = []
+    acc = 0
+    shift = 35
+    for c in s:
+        val = _b32rev.get(c)
+        if val is None:
+            raise TypeError('Non-base32 digit found')
+        acc += _b32rev[c] << shift
+        shift -= 5
+        if shift < 0:
+            parts.append(binascii.unhexlify('%010x' % acc))
+            acc = 0
+            shift = 35
+    # Process the last, partial quanta
+    last = binascii.unhexlify('%010x' % acc)
+    if padchars == 0:
+    # TODO hi
+        last = ''                       # No characters
+    elif padchars == 1:
+        last = last[:-1]
+    elif padchars == 3:
+        last = last[:-2]
+    elif padchars == 4:
+        last = last[:-3]
+    elif padchars == 6:
+        last = last[:-4]
+    else:
+        raise TypeError('Incorrect padding')
+    parts.append(last)
+    return EMPTYSTRING.join(parts)
+
+# blank line at end of file
+