From 7c010f6b4b14b9a37e4b4a2352b8e99d8b119e15 Mon Sep 17 00:00:00 2001 From: Scott McDonnell Date: Mon, 18 Sep 2017 11:41:39 +0100 Subject: [PATCH 1/4] added test for TypeScript and TypeScriptReact languages --- test_runner.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/test_runner.py b/test_runner.py index f64a07a..aba3bae 100644 --- a/test_runner.py +++ b/test_runner.py @@ -69,6 +69,40 @@ def assertDocBlockrResult(self, expected): self.assertEquals(expected, self.get_view_content()) +class TestTypeScript(ViewTestCase): + def get_syntax_file(self): + return 'Packages/TypeScript/TypeScript.tmLanguage' + + def test_parameters_are_added_to_function_templates(self): + self.set_view_content('/**|\nfunction foo (bar: number, baz: string): boolean {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {number} bar [description]', + ' * @param {string} baz [description]', + ' * @return {boolean} [description]', + ' */', + 'function foo (bar: number, baz: string): boolean {' + ]) + +class TestTypeScriptReact(ViewTestCase): + def get_syntax_file(self): + return 'Packages/TypeScript/TypeScriptReact.tmLanguage' + + def test_parameters_are_added_to_function_templates(self): + self.set_view_content('/**|\nfunction foo (bar: number, baz: string): boolean {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {number} bar [description]', + ' * @param {string} baz [description]', + ' * @return {boolean} [description]', + ' */', + 'function foo (bar: number, baz: string): boolean {' + ]) + class TestJavaScript(ViewTestCase): def get_syntax_file(self): @@ -428,9 +462,10 @@ def run(self): test_loader = unittest.TestLoader() # TODO move all test cases into tests directory and make test loader auto load testcases from the folder - - suite.addTests(test_loader.loadTestsFromTestCase(TestJavaScript)) - suite.addTests(test_loader.loadTestsFromTestCase(TestPHP)) + suite.addTests(test_loader.loadTestsFromTestCase(TestTypeScript)) + suite.addTests(test_loader.loadTestsFromTestCase(TestTypeScriptReact)) + #suite.addTests(test_loader.loadTestsFromTestCase(TestJavaScript)) + #suite.addTests(test_loader.loadTestsFromTestCase(TestPHP)) # TODO toggle test verbosity unittest.TextTestRunner(verbosity=1).run(suite) From 0cfe48fbcd4cb804e330c908f5774677013437ff Mon Sep 17 00:00:00 2001 From: Scott McDonnell Date: Mon, 18 Sep 2017 11:42:21 +0100 Subject: [PATCH 2/4] add .tsx files to the Typescript sourceLang so they are treated the same as .ts files --- jsdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsdocs.py b/jsdocs.py index 3f81588..80ad0e9 100644 --- a/jsdocs.py +++ b/jsdocs.py @@ -69,7 +69,7 @@ def getParser(view): return JsdocsJava(viewSettings) elif sourceLang == 'rust': return JsdocsRust(viewSettings) - elif sourceLang == 'ts': + elif sourceLang == 'ts' or sourceLang == 'tsx': return JsdocsTypescript(viewSettings) return JsdocsJavascript(viewSettings) From b0458252260ba040e4cfd5a4f351d1021cd45fbf Mon Sep 17 00:00:00 2001 From: Scott McDonnell Date: Mon, 18 Sep 2017 18:18:41 +0100 Subject: [PATCH 3/4] add typescript tests and fix for arrow functions and typed variables --- jsdocs.py | 132 +++++++++++++++++++---- test_runner.py | 283 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 391 insertions(+), 24 deletions(-) diff --git a/jsdocs.py b/jsdocs.py index 80ad0e9..9ffb5d5 100644 --- a/jsdocs.py +++ b/jsdocs.py @@ -373,6 +373,11 @@ def parse(self, line): def formatVar(self, name, val, valType=None): out = [] + + #for basic test with no value or type + if not val and not valType: + return out + if not valType: if not val or val == '': # quick short circuit valType = "[type]" @@ -1545,28 +1550,23 @@ def setupSettings(self): # technically, they can contain all sorts of unicode, but w/e "varIdentifier": identifier, "fnIdentifier": identifier, - "fnOpener": 'function(?:\\s+' + identifier + ')?\\s*\\(', + "typeIdentifier": parametric_type_identifier, + + "fnOpener": '(?:' + + r'function[\s*]*(?:' + identifier + r')?\s*\(' + + '|' + + '(?:' + identifier + r'|\(.*\)\s*=>)' + + '|' + + '(?:' + identifier + r'\s*\(.*\)\s*\{)' + + ')', + "commentCloser": " */", "bool": "Boolean", - "function": "Function", - "functionRE": - # Modifiers - r'(?:public|private|static)?\s*' - # Method name - + r'(?P' + identifier + r')\s*' - # Params - + r'\((?P.*)\)\s*' - # Return value - + r'(:\s*(?P' + parametric_type_identifier + r'))?', - "varRE": - r'((public|private|static|var)\s+)?(?P' + identifier - + r')\s*(:\s*(?P' + parametric_type_identifier - + r'))?(\s*=\s*(?P.*?))?([;,]|$)' + "function": "Function" } - self.functionRE = re.compile(self.settings['functionRE']) - self.varRE = re.compile(self.settings['varRE']) - def parseFunction(self, line): + def parseFunctionOld(self, line): + line = line.strip() res = self.functionRE.search(line) @@ -1575,6 +1575,64 @@ def parseFunction(self, line): group_dict = res.groupdict() return (group_dict["name"], group_dict["args"], group_dict["retval"]) + def parseFunction(self, line): + + normalFunctions = ( + # Normal functions... + # fnName = function, fnName : function + r'(?:(?P' + self.settings['varIdentifier'] + r')\s*[:=]\s*)?' + + 'function' + # function fnName, function* fnName + + r'(?P[\s*]+)?(?P' + self.settings['fnIdentifier'] + ')?' + # (arg1, arg2) + + r'\s*\(\s*(?P.*)\)' + # :type + + r'(:\s*(?P' + self.settings['typeIdentifier'] + r'))?' + ) + + arrowFunctions = ( + # foo = + r'(?:(?P' + self.settings['varIdentifier'] + r')\s*[:=]\s*)?' + # () => y, x => y, (x, y) => y, (x = 4) => y + + r'(?:(?P' + self.settings['varIdentifier'] + r')|\(\s*(?P.*)\))' + # :type + + r'(:\s*(?P' + self.settings['typeIdentifier'] + r'))?' + # arrow + + r'\s*=>' + ) + + initializerShorthand = ( + # ES6 method initializer shorthand + # var person: InterfaceA = { getName() { return this.name; } } + r'(?P' + self.settings['varIdentifier'] + + ')\s*\((?P.*)\)\s*\{' + ) + + res = re.search(normalFunctions, line + ) or re.search(arrowFunctions, line + ) or re.search(initializerShorthand, line + ) + if not res: + return None + + groups = { + 'name1': '', + 'name2': '', + 'generator': '', + 'args': '', + 'args2': '', + 'retval': '' + } + groups.update(res.groupdict()) + + # grab the name out of "name1 = function name2(foo)" preferring name1 + generatorSymbol = '*' if (groups['generator'] or '').find('*') > -1 else '' + name = generatorSymbol + (groups['name1'] or groups['name2'] or '') + args = groups['args'] or groups['args2'] or '' + retval = groups['retval'] or '' + + return (name, args, retval) + def getArgType(self, arg): if ':' in arg: return arg.split(':')[-1].strip() @@ -1585,13 +1643,43 @@ def getArgName(self, arg): arg = arg.split(':')[0] return arg.strip('[ \?]') + def getArgInfo(self, arg): + if (re.search('^\{.*\}$', arg)): + subItems = splitByCommas(arg[1:-1]) + prefix = 'options.' + else: + subItems = [arg] + prefix = '' + + out = [] + for subItem in subItems: + out.append((self.getArgType(subItem), prefix + self.getArgName(subItem))) + + return out + def parseVar(self, line): - res = self.varRE.search(line) + # var foo = blah, + # foo: string; + # foo: Array; + # baz.foo = blah; + # var foo = blah, + # baz = { + # foo : blah + # } + regex = ( + r'(?P' + self.settings['varIdentifier'] + ')' + # :type + + r'(:\s*(?P' + self.settings['typeIdentifier'] + r'))?' + # = value + + r'(\s*[=]\s*(?P.*?))?(?:[;,]|$)' + ) + + res = re.search(regex, line) if not res: return None - val = res.group('val') - if val: val = val.strip() - return (res.group('name'), val, res.group('type')) + + return (res.group('name'), res.group('val'), res.group('valType')) + def getFunctionReturnType(self, name, retval): return retval if retval != 'void' else None diff --git a/test_runner.py b/test_runner.py index aba3bae..9122ccc 100644 --- a/test_runner.py +++ b/test_runner.py @@ -73,7 +73,77 @@ class TestTypeScript(ViewTestCase): def get_syntax_file(self): return 'Packages/TypeScript/TypeScript.tmLanguage' + def test_basic(self): + self.set_view_content("\n/**|\nbasic") + self.run_doc_blockr() + self.assertDocBlockrResult('\n/**\n * \n */\nbasic') + + def test_empty_doc_blocks_are_created(self): + self.set_view_content('/**') + self.run_doc_blockr() + self.assertDocBlockrResult([ + "/**", + " * |CURSOR|", + " */" + ]) + + def test_that_function_template_is_added(self): + self.set_view_content('/**|\nfunction foo () {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @return {[type]} [description]', + ' */', + 'function foo () {' + ]) + + def test_that_function_return_type_is_added(self): + self.set_view_content('/**|\nfunction foo (): boolean {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @return {boolean} [description]', + ' */', + 'function foo (): boolean {' + ]) + + def test_that_function_return_void_is_not_added(self): + self.set_view_content('/**|\nfunction foo (): void {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' */', + 'function foo (): void {' + ]) + + def test_that_function_return_type_array_is_added(self): + self.set_view_content('/**|\nfunction foo (): Array {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @return {Array} [description]', + ' */', + 'function foo (): Array {' + ]) + def test_parameters_are_added_to_function_templates(self): + self.set_view_content('/**|\nfunction foo (bar, baz) {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {[type]} bar [description]', + ' * @param {[type]} baz [description]', + ' * @return {[type]} [description]', + ' */', + 'function foo (bar, baz) {' + ]) + + def test_parameter_types_are_added_to_function_templates(self): self.set_view_content('/**|\nfunction foo (bar: number, baz: string): boolean {') self.run_doc_blockr() self.assertDocBlockrResult([ @@ -86,10 +156,219 @@ def test_parameters_are_added_to_function_templates(self): 'function foo (bar: number, baz: string): boolean {' ]) + def test_parameter_types_are_added_to_arrow_functions(self): + self.set_view_content('/**|\nvar foo = (bar: number, callback: string => void): boolean => {') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {number} bar [description]', + ' * @param {string => void} callback [description]', + ' * @return {boolean} [description]', + ' */', + 'var foo = (bar: number, callback: string => void): boolean => {' + ]) + + + def test_parameters_are_added_to_function_template_with_description_disabled(self): + self.set_view_content('/**|\nfunction foo (bar, baz) {') + self.view.settings().set('jsdocs_function_description', False) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * @param |SELECTION_BEGIN|{[type]}|SELECTION_END| bar [description]', + ' * @param {[type]} baz [description]', + ' * @return {[type]} [description]', + ' */', + 'function foo (bar, baz) {' + ]) + + def test_parameters_are_added_to_function_template_with_description_disabled_and_spacers_between_sections(self): + self.set_view_content('/**|\nfunction foo (bar, baz) {') + self.view.settings().set('jsdocs_function_description', False) + self.view.settings().set('jsdocs_spacer_between_sections', True) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * @param |SELECTION_BEGIN|{[type]}|SELECTION_END| bar [description]', + ' * @param {[type]} baz [description]', + ' *', + ' * @return {[type]} [description]', + ' */', + 'function foo (bar, baz) {' + ]) + + def test_parameters_are_added_to_function_template_with_description_disabled_and_spacer_after_description_isset(self): + self.set_view_content('/**|\nfunction foo (bar, baz) {') + self.view.settings().set('jsdocs_function_description', False) + self.view.settings().set('jsdocs_spacer_between_sections', 'after_description') + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * @param |SELECTION_BEGIN|{[type]}|SELECTION_END| bar [description]', + ' * @param {[type]} baz [description]', + ' * @return {[type]} [description]', + ' */', + 'function foo (bar, baz) {' + ]) + + def test_params_across_multiple_lines_should_be_identified(self): + self.set_view_content([ + '/**|', + 'function foo(bar: number,', + ' baz:number,', + ' quux: string', + ' ):ClassA {' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {number} bar [description]', + ' * @param {number} baz [description]', + ' * @param {string} quux [description]', + ' * @return {ClassA} [description]', + ' */', + 'function foo(bar: number,', + ' baz:number,', + ' quux: string', + ' ):ClassA {' + ]) + + def test_vars_initialised_to_number_get_placeholders(self): + self.set_view_content([ + '/**|', + 'var foo = 1;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {Number}', + ' */', + 'var foo = 1;' + ]) + + def test_vars_string_double_quotes(self): + self.set_view_content([ + '/**|', + 'var foo = "a";' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {String}', + ' */', + 'var foo = "a";' + ]) + + def test_vars_string_single_quotes(self): + self.set_view_content([ + '/**|', + 'var foo = \'a\';' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {String}', + ' */', + 'var foo = \'a\';' + ]) + + def test_vars_unknown_type(self): + self.set_view_content([ + '/**|', + 'var foo = bar;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {[type]}', + ' */', + 'var foo = bar;' + ]) + + def test_vars_set_type_number(self): + self.set_view_content([ + '/**|', + 'var foo: number;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {number}', + ' */', + 'var foo: number;' + ]) + + def test_vars_const(self): + self.set_view_content([ + '/**|', + 'const foo: String = "THIS IS A STRING";' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {String}', + ' */', + 'const foo: String = "THIS IS A STRING";' + ]) + + def test_vars_let(self): + self.set_view_content([ + '/**|', + 'let foo: Bar;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {Bar}', + ' */', + 'let foo: Bar;' + ]) + + def test_vars_set_type_no_space(self): + self.set_view_content([ + '/**|', + 'var foo:number;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {number}', + ' */', + 'var foo:number;' + ]) + + def test_vars_set_type_class(self): + self.set_view_content([ + '/**|', + 'var foo: ClassA;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {ClassA}', + ' */', + 'var foo: ClassA;' + ]) + + class TestTypeScriptReact(ViewTestCase): def get_syntax_file(self): return 'Packages/TypeScript/TypeScriptReact.tmLanguage' + """ + this failed until .tsx was added to the TypeScript sourceLang + """ def test_parameters_are_added_to_function_templates(self): self.set_view_content('/**|\nfunction foo (bar: number, baz: string): boolean {') self.run_doc_blockr() @@ -464,8 +743,8 @@ def run(self): # TODO move all test cases into tests directory and make test loader auto load testcases from the folder suite.addTests(test_loader.loadTestsFromTestCase(TestTypeScript)) suite.addTests(test_loader.loadTestsFromTestCase(TestTypeScriptReact)) - #suite.addTests(test_loader.loadTestsFromTestCase(TestJavaScript)) - #suite.addTests(test_loader.loadTestsFromTestCase(TestPHP)) + suite.addTests(test_loader.loadTestsFromTestCase(TestJavaScript)) + suite.addTests(test_loader.loadTestsFromTestCase(TestPHP)) # TODO toggle test verbosity unittest.TextTestRunner(verbosity=1).run(suite) From 91b8abc9195fb0366a5f2f937f79893f7db28a83 Mon Sep 17 00:00:00 2001 From: Scott McDonnell Date: Tue, 19 Sep 2017 11:12:16 +0100 Subject: [PATCH 4/4] add support for Union Types with tests --- jsdocs.py | 13 ++----------- test_runner.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/jsdocs.py b/jsdocs.py index 9ffb5d5..c01f131 100644 --- a/jsdocs.py +++ b/jsdocs.py @@ -1541,7 +1541,7 @@ class JsdocsTypescript(JsdocsParser): def setupSettings(self): identifier = '[a-zA-Z_$][a-zA-Z_$0-9]*' base_type_identifier = r'%s(\.%s)*(\[\])?' % ((identifier, ) * 2) - parametric_type_identifier = r'%s(\s*<\s*%s(\s*,\s*%s\s*)*>)?' % ((base_type_identifier, ) * 3) + parametric_type_identifier = r'((\s*[|]?\s*%s(\s*<\s*%s(\s*,\s*%s\s*)*>)?)+)' % ((base_type_identifier, ) * 3) self.settings = { # curly brackets around the type information "curlyTypes": True, @@ -1565,16 +1565,6 @@ def setupSettings(self): "function": "Function" } - def parseFunctionOld(self, line): - - line = line.strip() - res = self.functionRE.search(line) - - if not res: - return None - group_dict = res.groupdict() - return (group_dict["name"], group_dict["args"], group_dict["retval"]) - def parseFunction(self, line): normalFunctions = ( @@ -1658,6 +1648,7 @@ def getArgInfo(self, arg): return out def parseVar(self, line): + # var foo = blah, # foo: string; # foo: Array; diff --git a/test_runner.py b/test_runner.py index 9122ccc..a3cb93a 100644 --- a/test_runner.py +++ b/test_runner.py @@ -361,14 +361,54 @@ def test_vars_set_type_class(self): 'var foo: ClassA;' ]) + def test_var_with_union_types(self): + self.set_view_content([ + '/**|', + 'var foo: ClassA | ClassB;' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @type {ClassA | ClassB}', + ' */', + 'var foo: ClassA | ClassB;' + ]) + + def test_return_value_with_union_types(self): + self.set_view_content([ + '/**|', + 'function foo(): ClassA | null {' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @return {ClassA | null} [description]', + ' */', + 'function foo(): ClassA | null {' + ]) + + def test_param_values_with_union_types(self): + self.set_view_content([ + '/**|', + 'function foo (input: string[] | null | Array): void {' + ]) + self.run_doc_blockr() + self.assertDocBlockrResult([ + '/**', + ' * |SELECTION_BEGIN|[foo description]|SELECTION_END|', + ' * @param {string[] | null | Array} input [description]', + ' */', + 'function foo (input: string[] | null | Array): void {' + ]) + class TestTypeScriptReact(ViewTestCase): def get_syntax_file(self): return 'Packages/TypeScript/TypeScriptReact.tmLanguage' - """ - this failed until .tsx was added to the TypeScript sourceLang - """ + # .tsx added to the TypeScript sourceLang def test_parameters_are_added_to_function_templates(self): self.set_view_content('/**|\nfunction foo (bar: number, baz: string): boolean {') self.run_doc_blockr() @@ -381,6 +421,7 @@ def test_parameters_are_added_to_function_templates(self): ' */', 'function foo (bar: number, baz: string): boolean {' ]) + class TestJavaScript(ViewTestCase):