├── .gitignore ├── .python-version ├── README.md ├── _ctags_tester.py ├── ctags.conf ├── ctags_spec.py ├── requirements.txt └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.5.1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exuberant CTAGS configuration for JavaScript and CoffeeScript 2 | 3 | [Exuberant CTAGS](http://ctags.sourceforge.net) doesn't support CoffeeScript 4 | natively and its JavaScript support doesn't handle modern usage. 5 | 6 | There are a bunch of sample regex's around to solve this. However I found 7 | they worked most of the time and would fail for some situations. Different sets of 8 | configurations would fail for different bits of code. How can we unify them? 9 | 10 | I wrote some automated tests to check all the different scenarios as any 11 | agile programmer would. Now we can adjust the configuration and see that 12 | all our situations are handled. 13 | 14 | The regex rules in ctags.conf will support these idioms: 15 | 16 | ## JavaScript 17 | Functions & Methods in various forms: 18 | ``` 19 | function global_function(a, b){} -> global_function 20 | var object_class = { -> object_class 21 | constructor: function(){} 22 | object_method: function(){} -> object_method 23 | } 24 | var assigned_function = function(){} -> asigned_function 25 | Namespace.namespaced_func = function() {} -> namespaced_function 26 | ``` 27 | 28 | Variables, arrays, objects: 29 | ``` 30 | var myarray = [1, 2]; -> myarray 31 | var myobject = {a: 1}; -> myobject 32 | var myvar = 1; -> myvar 33 | ``` 34 | 35 | jQuery `bind` event handlers. Because the extracted symbol names include both the 36 | selector and the event, they are not useful for searches, but are very handy in 37 | the TagBar window to see your file's structure. 38 | ``` 39 | $("#foo").bind("dollar_bind_event", function() { -> "#foo".dollar_bind_event 40 | jQuery('#foo').bind("jquery_bind_event", function() { -> '#foo'.jquery_bind_event 41 | $(bar).bind("var_bind_event", function() { -> bar.var_bind_event 42 | ``` 43 | 44 | Symbols in RSpec style tests. 45 | `describe()` blocks use the first parameter as the symbol name. 46 | `context()` and `it()` blocks include the line's indentation for use in TagBar 47 | so you can sort of see the file's nested structure 48 | ``` 49 | describe("Dog", function() { -> Dog 50 | describe("bark", function() { -> bark 51 | context("while running", function() { -> . while running 52 | it("is loud", function() { -> . is loud 53 | ``` 54 | 55 | ## Coffeescript 56 | Classes and functions are identified. When there is a namespace 57 | prefix, or an `@` instance prefix, the symbol name is correctly extracted. 58 | ``` 59 | create: -> -> create 60 | @_setWorkspaceXml: (workspace, codeXml) -> -> _setWorkspaceXml 61 | class stratego.CamperAppEditor extends phaser.State -> CamperAppEditor 62 | local_function = (gfx, focusObj) -> -> local_function 63 | window.global_function = (gfx, focusObj) -> -> global_function 64 | window.pkg.pkg_function = (gfx, focusObj) -> -> pkg_function 65 | ``` 66 | 67 | This configuration started from here: 68 | 69 | - https://github.com/majutsushi/tagbar/wiki#exuberant-ctags-vanilla 70 | - https://github.com/majutsushi/tagbar/wiki#coffeescript 71 | 72 | Incedentally, if you are interested in electronics, check out [Electropoclypse](http://electropocalypse.com) 73 | -------------------------------------------------------------------------------- /_ctags_tester.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import subprocess 3 | import tempfile 4 | import re 5 | import unittest 6 | 7 | class CTagsTester: 8 | ''' 9 | Run ctags using, capture output, and check for symbols in the resulting tags list 10 | 11 | Ctags output should be 12 | \t \t \t 13 | ''' 14 | 15 | def __init__(self, testcase, lang_suffix, ctags_conf_fname, source_code): 16 | self.testcase = testcase 17 | self.testcase.assertTrue(os.path.exists(ctags_conf_fname), "Expect {} to exist".format(ctags_conf_fname)) 18 | self.filepath, self.ctags_out = self._ctags_for(lang_suffix, ctags_conf_fname, source_code) 19 | 20 | def check(self, expect_symbol, expect_symbol_type, expect_vim_search_cmd=None): 21 | # should produce this line in the 'tags' file 22 | with self.testcase.subTest(expect_symbol=expect_symbol, expect_vim_search_cmd=expect_vim_search_cmd): 23 | expected_line = '^{symbol}\\t{filepath}\\t{search_cmd}\\t{symbol_type}'.format( 24 | symbol = re.escape(expect_symbol), 25 | filepath = re.escape(self.filepath), 26 | search_cmd = re.escape(expect_vim_search_cmd) if expect_vim_search_cmd else '[^\t]*', 27 | symbol_type = re.escape(expect_symbol_type)) 28 | err_msg = "\n--- Expect this line:\n {}\n--- to be in ctags output:\n{}\n---\n".format(expected_line, indent(self.ctags_out, 4)) 29 | search_successful = re.search(expected_line, self.ctags_out, re.MULTILINE) 30 | self.testcase.assertIsNotNone(search_successful, err_msg) 31 | 32 | # Check that only one line for the symbol was produced 33 | with self.testcase.subTest(expect_symbol_is_unique=expect_symbol): 34 | lines = self.ctags_out.split('\n') 35 | occurances = [line for line in lines if line.startswith(expect_symbol + '\t')] 36 | num_found = len(occurances) 37 | err_msg = "\n--- Expect '{}' to appear once but found {} in ctags output:\n{}\n---\n".format( 38 | expect_symbol, num_found, indent(self.ctags_out, 4)) 39 | self.testcase.assertEqual(1, num_found, err_msg ) 40 | 41 | @staticmethod 42 | def _ctags_for(lang_suffix, ctags_conf_fname, code_sample): 43 | '''Run ctags and return [temporary source file's filename, output]''' 44 | with tempfile.NamedTemporaryFile(mode='w', suffix=lang_suffix, delete=False) as f: 45 | f.write(code_sample) 46 | 47 | return f.name, CTagsTester._run_ctags(ctags_conf_fname, f.name) 48 | 49 | @staticmethod 50 | def _run_ctags(ctags_conf_fname, sample_filename): 51 | '''Run ctags and return output as string''' 52 | out = subprocess.check_output(['ctags', 53 | '--options=NONE', # ignore other configuration files 54 | '--options=' + ctags_conf_fname, 55 | '-f-', 56 | sample_filename], 57 | stderr=subprocess.STDOUT # Hide this message: "No options will be read from files or environment" 58 | ) 59 | result = out.decode('utf-8') 60 | return result 61 | 62 | def indent(s, num_spaces): 63 | spaces = ' ' * num_spaces 64 | lines = s.split('\n') 65 | indentedLines = [spaces + l for l in lines] 66 | return '\n'.join(indentedLines) 67 | 68 | -------------------------------------------------------------------------------- /ctags.conf: -------------------------------------------------------------------------------- 1 | --languages=-JavaScript 2 | --langdef=js 3 | --langmap=js:.js 4 | --regex-js=/^(var)?[ \t]*([A-Za-z0-9_$]*\.)*([A-Za-z0-9_$]+)[ \t]*=[ \t]*\[/\3/a,array/ 5 | --regex-js=/^(var)?[ \t]*([A-Za-z0-9_$]*\.)*([A-Za-z0-9_$]+)[ \t]*=[ \t]*\{/\3/o,object/ 6 | --regex-js=/^(var)?[ \t]*([A-Za-z0-9_$]*\.)*([A-Za-z0-9_$]+)[ \t]*=[^{\[]*$/\3/r,var/ 7 | 8 | --regex-js=/^var[ \t]+([A-Za-z0-9._$]+)[ \t]*=[ \t]*[A-Za-z0-9_$]+.extend/\1/f,function/ 9 | --regex-js=/^[ \t]*([A-Za-z0-9_$]*\.)*([A-Za-z0-9_$]+)[ \t]*[:=][ \t]*function/\2/f,function/ 10 | --regex-js=/^[ \t]*function[ \t]*([A-Za-z0-9_$]+)[ \t]*\(/\1/f,function/ 11 | --regex-js=/^[ \t]*var[ \t]+([A-Za-z0-9_$]*\.)*([A-Za-z0-9_$]+)[ \t]*=[ \t]function/\2/f,function/ 12 | 13 | --regex-js=/(jQuery|\$)\([ \t]*([^ \t]*)[ \t]*\)\.bind\([ \t]*['"](.*)['"]/\2.\3/f,function/ 14 | 15 | --regex-js=/^[ \t]*describe[ \t]*\([ \t]*["'](.*)["']/\1/f,function/ 16 | --regex-js=/^([ \t]*)(describe|context|it)[ \t]*\([ \t]*["'](.*)["']/.\1\3/f,function/ 17 | 18 | 19 | --langdef=coffee 20 | --langmap=coffee:.coffee.cjsx 21 | --regex-coffee=/(^|=[ \t])*class ([A-Za-z_][A-Za-z0-9_]*\.)*([A-Za-z_][A-Za-z0-9_]*)( extends ([A-Za-z][A-Za-z0-9_.]*)+)?$/\3/c,class/ 22 | --regex-coffee=/^[ \t]*(module\.)?(exports\.)?@?(([A-Za-z_][A-Za-z0-9_.]*)+):.*[-=]>.*$/\3/m,method/ 23 | --regex-coffee=/^[ \t]*([A-Za-z_][A-Za-z0-9_]*\.)*([A-Za-z_][A-Za-z0-9_]*)[ \t]*=.*[-=]>.*$/\2/f,function/ 24 | --regex-coffee=/^[ \t]*([A-Za-z_][A-Za-z0-9_]*\.)*([A-Za-z_][A-Za-z0-9_]*)[ \t]*=[^->]*$/\2/v,variable/ 25 | --regex-coffee=/^[ \t]*@(([A-Za-z_][A-Za-z0-9_.]*)+)[ \t]*=[^->\n]*$/\1/a,attribute/ 26 | --regex-coffee=/^[ \t]*@(([A-Za-z_][A-Za-z0-9_.]*)+):[^->\n]*$/\1/A,static attribute/ 27 | --regex-coffee=/^[ \t]*(([A-Za-z_][A-Za-z0-9_.]*)+):[^->\n]*$/\1/a,attribute/ 28 | --regex-coffee=/((constructor|initialize):[ \t]*\()@(([A-Za-z][A-Za-z0-9_.]*)+)([ \t]*=[ \t]*[^,)]+)?/\3/a,attribute/ 29 | 30 | 31 | 32 | --langdef=scss 33 | --langmap=scss:.scss 34 | --regex-scss=/^[ \t]*@mixin[ \t]+([A-Za-z0-9_]+)/\1/m,mixin/ 35 | --regex-scss=/^[ \t]*@function[ \t]+([A-Za-z0-9_]+)/\1/f,function/ 36 | 37 | -------------------------------------------------------------------------------- /ctags_spec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Rewritten tests using [expectorant](https://github.com/winstonwolff/expectorant) 4 | 5 | import unittest 6 | import tempfile 7 | import re 8 | import os.path 9 | import subprocess 10 | import tempfile 11 | from expectorant import * 12 | 13 | CTAGS_CONF = 'ctags.conf' 14 | 15 | @describe('ctags for CoffeeScript') 16 | def _(): 17 | 18 | SCENARIOS = ( 19 | # (code symbol type), 20 | (' @_privateClassMethod: (workspace, codeXml) ->', '_privateClassMethod', 'm'), 21 | 22 | ('class package.SampleClass extends base.class', 'SampleClass', 'c'), 23 | ('SampleClass = React.createClass', 'SampleClass', 'v'), 24 | ('exports.SampleClass = React.createClass', 'SampleClass', 'v'), 25 | ('exports.SampleClass = SampleClass = React.createClass', 'SampleClass', 'v'), 26 | ('exports.SampleClass = R.createClass', 'SampleClass', 'v'), 27 | 28 | ('local_function = (gfx, focusObj) ->', 'local_function', 'f'), 29 | ('window.global_function = (gfx, focusObj) ->', 'global_function', 'f'), 30 | ('window.pkg.pkg_function = (gfx, focusObj) ->', 'pkg_function', 'f'), 31 | ('module.module_function = (a, b) ->', 'module_function', 'f'), 32 | ('exports.exports_function = (a, b) ->', 'exports_function', 'f'), 33 | ) 34 | 35 | @it('finds symbols like {1} in source like "{0}"', repeat=SCENARIOS) 36 | def _(source, expect_symbol, expect_symbol_type): 37 | 38 | tag_output = run_ctags('.coffee', CTAGS_CONF, source) 39 | 40 | print(">>> SOURCE:\n", source, "\n--- OUTPUT:\n", tag_output, "\n<<<", sep="") 41 | 42 | matches = symbol_and_type_matches(tag_output, expect_symbol, expect_symbol_type) 43 | expect(len(matches)) == 1 44 | 45 | matches = symbol_matches(tag_output, expect_symbol) 46 | expect(len(matches)) == 1 47 | 48 | @it('finds two symbols when defined twice') 49 | def _(): 50 | source = ''' 51 | exports.double_func = (a, b) -> 52 | exports.double_func = (c, d) ->''' 53 | tag_output = run_ctags('.coffee', CTAGS_CONF, source) 54 | 55 | matches = symbol_and_type_matches(tag_output, 'double_func', 'f') 56 | 57 | expect(len(matches)) == 2 58 | 59 | @describe('ctags for JavaScript') 60 | def _(): 61 | scope = None 62 | @before 63 | def _(): 64 | nonlocal scope 65 | scope = Scope() 66 | 67 | @context('with functions defined in several forms') 68 | def _(): 69 | 70 | @before 71 | def _(): 72 | scope.source = ''' 73 | function global_function(a, b){} 74 | 75 | var object_class = { 76 | constructor: function(){} 77 | object_method: function(){} 78 | } 79 | 80 | var assigned_function = function(){} 81 | 82 | Namespace.namespaced_func = function (game, x, y, key, frame) {} 83 | ''' 84 | scope.tag_output = run_ctags('.js', CTAGS_CONF, scope.source) 85 | 86 | @it('finds global_function') 87 | def _(): 88 | expect(len(symbol_and_type_matches(scope.tag_output, 'global_function', 'f'))) == 1 89 | 90 | @it('finds object_method') 91 | def _(): 92 | expect(len(symbol_and_type_matches(scope.tag_output, 'object_method', 'f'))) == 1 93 | 94 | @it('finds assigned_function') 95 | def _(): 96 | expect(len(symbol_and_type_matches(scope.tag_output, 'assigned_function', 'f'))) == 1 97 | 98 | @it('finds namespaced_func') 99 | def _(): 100 | expect(len(symbol_and_type_matches(scope.tag_output, 'namespaced_func', 'f'))) == 1 101 | 102 | @context('with variables, arrays, and objects') 103 | def _(): 104 | 105 | @before 106 | def _(): 107 | scope.source = ''' 108 | var myarray = [1, 2]; 109 | var myobject = {a: 1}; 110 | var myvar = 1; 111 | var myfunc = function(){}; 112 | 113 | var indented_array = [1, 2]; 114 | var indented_object = {a: 1}; 115 | var indented_var = 1; 116 | var indented_func = function(){}; 117 | ''' 118 | scope.tag_output = run_ctags('.js', CTAGS_CONF, scope.source) 119 | 120 | @it('finds arrays') 121 | def _(): 122 | expect(len(symbol_and_type_matches(scope.tag_output, 'myarray', 'a'))) == 1 123 | 124 | @it('finds objects') 125 | def _(): 126 | expect(len(symbol_and_type_matches(scope.tag_output, 'myobject', 'o'))) == 1 127 | 128 | @it('finds variables') 129 | def _(): 130 | expect(len(symbol_and_type_matches(scope.tag_output, 'myvar', 'r'))) == 1 131 | 132 | @it('finds functions') 133 | def _(): 134 | expect(len(symbol_and_type_matches(scope.tag_output, 'myfunc', 'f'))) == 1 135 | 136 | @it('finds indented_array') 137 | def _(): 138 | expect(len(symbol_and_type_matches(scope.tag_output, 'indented_array', 'a'))) == 1 139 | 140 | @it('finds indented_object') 141 | def _(): 142 | expect(len(symbol_and_type_matches(scope.tag_output, 'indented_object', 'o'))) == 1 143 | 144 | @it('finds indented_var') 145 | def _(): 146 | expect(len(symbol_and_type_matches(scope.tag_output, 'indented_var', 'r'))) == 1 147 | 148 | @it('finds indented_func') 149 | def _(): 150 | expect(len(symbol_and_type_matches(scope.tag_output, 'indented_func', 'f'))) == 1 151 | 152 | @context('with jquery style bindings') 153 | def _(): 154 | pass 155 | 156 | 157 | @context('with rspec style Jasmine or Mocha tests') 158 | def _(): 159 | pass 160 | 161 | @describe('ctags for SCSS') 162 | def _(): 163 | pass 164 | 165 | 166 | def symbol_and_type_matches(ctags_output, expect_symbol, expect_symbol_type): 167 | ''' 168 | Matcher for ctags output 169 | ''' 170 | expected_line = '^{symbol}\\t[^\\t]*\\t[^\\t]*\\t{symbol_type}'.format( 171 | symbol = re.escape(expect_symbol), 172 | symbol_type = re.escape(expect_symbol_type)) 173 | matches = re.findall(expected_line, ctags_output, re.MULTILINE) 174 | return matches 175 | 176 | def symbol_matches(ctags_output, expect_symbol): 177 | ''' 178 | Matcher for ctags output 179 | ''' 180 | expected_line = '^{symbol}'.format(symbol = re.escape(expect_symbol)) 181 | matches = re.findall(expected_line, ctags_output, re.MULTILINE) 182 | return matches 183 | 184 | 185 | def run_ctags(lang_suffix, ctags_conf_fname, code_sample): 186 | ''' 187 | Run ctags and return output as string 188 | ''' 189 | 190 | with tempfile.NamedTemporaryFile(mode='w', suffix=lang_suffix, delete=False) as f: 191 | f.write(code_sample) 192 | 193 | out = subprocess.check_output(['ctags', 194 | '--options=NONE', # ignore other configuration files 195 | '--options=' + ctags_conf_fname, 196 | '-f-', 197 | f.name], 198 | stderr=subprocess.STDOUT # Hide message: "No options will be read from files or environment" 199 | ) 200 | 201 | result = out.decode('utf-8') 202 | return result 203 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | expectorant 2 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import _ctags_tester 5 | 6 | CTAGS_CONF = 'ctags.conf' 7 | 8 | 9 | class CoffeescriptTest(unittest.TestCase): 10 | 11 | def ctags_tester(self, source_code): 12 | return _ctags_tester.CTagsTester(self, '.coffee', CTAGS_CONF, source_code) 13 | 14 | def test_class_method(self): 15 | c = self.ctags_tester(' create: ->') 16 | c.check( 17 | expect_symbol='create', 18 | expect_vim_search_cmd='/^ create: ->/;"', 19 | expect_symbol_type='m') 20 | 21 | def test_at_class_method(self): 22 | c = self.ctags_tester(' @_setWorkspaceXml: (workspace, codeXml) ->') 23 | c.check( 24 | expect_symbol='_setWorkspaceXml', 25 | expect_vim_search_cmd='/^ @_setWorkspaceXml: (workspace, codeXml) ->/;"', 26 | expect_symbol_type='m') 27 | 28 | def test_class_constructor(self): 29 | c = self.ctags_tester('class stratego.CamperAppEditor extends phaser.State') 30 | c.check( 31 | expect_symbol='CamperAppEditor', 32 | expect_vim_search_cmd='/^class stratego.CamperAppEditor extends phaser.State/;"', 33 | expect_symbol_type='c') 34 | 35 | def test_local_function(self): 36 | c = self.ctags_tester('local_function = (gfx, focusObj) ->') 37 | c.check( 38 | expect_symbol='local_function', 39 | expect_vim_search_cmd='/^local_function = (gfx, focusObj) ->/;"', 40 | expect_symbol_type='f') 41 | 42 | def test_global_function(self): 43 | c = self.ctags_tester('window.global_function = (gfx, focusObj) ->') 44 | c.check( 45 | expect_symbol='global_function', 46 | expect_vim_search_cmd='/^window.global_function = (gfx, focusObj) ->/;"', 47 | expect_symbol_type='f') 48 | 49 | def test_pkg_function(self): 50 | c = self.ctags_tester('window.pkg.pkg_function = (gfx, focusObj) ->') 51 | c.check( 52 | expect_symbol='pkg_function', 53 | expect_vim_search_cmd='/^window.pkg.pkg_function = (gfx, focusObj) ->/;"', 54 | expect_symbol_type='f') 55 | 56 | def test_module_function(self): 57 | c = self.ctags_tester('module.module_function = (a, b) ->') 58 | c.check( 59 | expect_symbol='module_function', 60 | expect_vim_search_cmd='/^module.module_function = (a, b) ->/;"', 61 | expect_symbol_type='f') 62 | 63 | def test_exports_function(self): 64 | c = self.ctags_tester('exports.exports_function = (a, b) ->') 65 | c.check( 66 | expect_symbol='exports_function', 67 | expect_vim_search_cmd='/^exports.exports_function = (a, b) ->/;"', 68 | expect_symbol_type='f') 69 | 70 | 71 | class JavascriptTest(unittest.TestCase): 72 | 73 | def ctags_tester(self, source_code): 74 | return _ctags_tester.CTagsTester(self, '.js', CTAGS_CONF, source_code) 75 | 76 | def test_functions(self): 77 | c = self.ctags_tester(''' 78 | function global_function(a, b){} 79 | 80 | var object_class = { 81 | constructor: function(){} 82 | object_method: function(){} 83 | } 84 | 85 | var assigned_function = function(){} 86 | 87 | Namespace.namespaced_func = function (game, x, y, key, frame) {} 88 | ''') 89 | c.check( 90 | expect_symbol='global_function', 91 | expect_vim_search_cmd='/^function global_function(a, b){}$/;"', 92 | expect_symbol_type='f') 93 | c.check( 94 | expect_symbol='object_method', 95 | expect_vim_search_cmd='/^ object_method: function(){}$/;"', 96 | expect_symbol_type='f') 97 | c.check( 98 | expect_symbol='assigned_function', 99 | expect_vim_search_cmd='/^var assigned_function = function(){}$/;"', 100 | expect_symbol_type='f') 101 | c.check( 102 | expect_symbol='namespaced_func', 103 | expect_vim_search_cmd='/^Namespace.namespaced_func = function (game, x, y, key, frame) {}$/;"', 104 | expect_symbol_type='f') 105 | 106 | 107 | def test_var(self): 108 | ''' 109 | Create tags for variables, arrays, and objects. 110 | ''' 111 | c = self.ctags_tester(''' 112 | var myarray = [1, 2]; 113 | var myobject = {a: 1}; 114 | var myvar = 1; 115 | var myfunc = function(){}; 116 | ''') 117 | c.check( 118 | expect_symbol='myarray', 119 | expect_vim_search_cmd='/^var myarray = [1, 2];$/;"', 120 | expect_symbol_type='a') 121 | c.check( 122 | expect_symbol='myobject', 123 | expect_vim_search_cmd='/^var myobject = {a: 1};$/;"', 124 | expect_symbol_type='o') 125 | c.check( 126 | expect_symbol='myvar', 127 | expect_vim_search_cmd='/^var myvar = 1;$/;"', 128 | expect_symbol_type='r') 129 | c.check( 130 | expect_symbol='myfunc', 131 | expect_vim_search_cmd='/^var myfunc = function(){};$/;"', 132 | expect_symbol_type='f') 133 | 134 | def test_namespaced_vars(self): 135 | c = self.ctags_tester(''' 136 | my_namespace.myarray = [1, 2]; 137 | my_namespace.myobject = {a: 1}; 138 | my_namespace.myvar = 1; 139 | my_namespace.myfunc = function(){}; 140 | ''') 141 | c.check( 142 | expect_symbol='myarray', 143 | expect_vim_search_cmd='/^my_namespace.myarray = [1, 2];$/;"', 144 | expect_symbol_type='a') 145 | c.check( 146 | expect_symbol='myobject', 147 | expect_vim_search_cmd='/^my_namespace.myobject = {a: 1};$/;"', 148 | expect_symbol_type='o') 149 | c.check( 150 | expect_symbol='myvar', 151 | expect_vim_search_cmd='/^my_namespace.myvar = 1;$/;"', 152 | expect_symbol_type='r') 153 | c.check( 154 | expect_symbol='myfunc', 155 | expect_vim_search_cmd='/^my_namespace.myfunc = function(){};$/;"', 156 | expect_symbol_type='f') 157 | 158 | def test_jquery(self): 159 | ''' 160 | Tags for jQuery bind() handlers. 161 | 162 | It creates symbols like `#foo.click` which is great for viewing in TagBar 163 | although less useful for jumping to tags. 164 | ''' 165 | c = self.ctags_tester(''' 166 | $("#foo").bind("dollar_bind_event", function() { 167 | jQuery('#foo').bind("jquery_bind_event", function() { 168 | $(bar).bind("var_bind_event", function() { 169 | jQuery( '#with_spaces' ).bind( "jquery_bind_event" , function() { 170 | ''') 171 | # $("#foo").click('click_event', function() { 172 | # $("#foo").dblclick('click_event', function() { 173 | # $("#foo").focus('click_event', function() { 174 | # $("#foo").focusin('click_event', function() { 175 | # $("#foo").focusout('click_event', function() { 176 | # $("#foo").hover('click_event', function() { 177 | # $("#foo").keydown('click_event', function() { 178 | # $("#foo").keypress('click_event', function() { 179 | # $("#foo").keyup('click_event', function() { 180 | # ''') 181 | c.check( 182 | expect_symbol='"#foo".dollar_bind_event', 183 | expect_symbol_type='f') 184 | c.check( 185 | expect_symbol="'#foo'.jquery_bind_event", 186 | expect_symbol_type='f') 187 | c.check( 188 | expect_symbol="bar.var_bind_event", 189 | expect_symbol_type='f') 190 | c.check( 191 | expect_symbol="'#with_spaces'.jquery_bind_event", 192 | expect_symbol_type='f') 193 | 194 | def test_rspec_style_tests(self): 195 | ''' 196 | Tags for blocks of Jasmine tests. 197 | ''' 198 | 199 | c = self.ctags_tester(''' 200 | describe("Dog", function() { 201 | describe("bark", function() { 202 | context("while running", function() { 203 | it("is loud", function() { 204 | }); 205 | }); 206 | }); 207 | }); 208 | ''') 209 | # Don't include indentation for `describe` so searching for tag will reveal specs. 210 | c.check( 211 | expect_symbol='Dog', 212 | expect_symbol_type='f') 213 | c.check( 214 | expect_symbol='bark', 215 | expect_symbol_type='f') 216 | 217 | # For `context` and `it` blocks, include indentation so TagBar's output shows file structure 218 | c.check( 219 | expect_symbol='. while running', 220 | expect_symbol_type='f') 221 | c.check( 222 | expect_symbol='. is loud', 223 | expect_symbol_type='f') 224 | 225 | class SCSSTest(unittest.TestCase): 226 | def ctags_tester(self, source_code): 227 | return _ctags_tester.CTagsTester(self, '.scss', CTAGS_CONF, source_code) 228 | 229 | def test_mixin(self): 230 | c = self.ctags_tester(''' 231 | @mixin medium_text {} 232 | @function my_function {} 233 | ''') 234 | c.check( 235 | expect_symbol='medium_text', 236 | expect_symbol_type='m') 237 | c.check( 238 | expect_symbol='my_function', 239 | expect_symbol_type='f') 240 | 241 | 242 | if __name__=='__main__': 243 | unittest.main() 244 | --------------------------------------------------------------------------------