Skip to content

Commit f80b0a8

Browse files
committed
Add typed params and return type support
1 parent 5278100 commit f80b0a8

File tree

5 files changed

+218
-50
lines changed

5 files changed

+218
-50
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,27 @@ and puts your jsdoc above it:
5151
```viml
5252
nmap <silent> <C-l> ?function<cr>:noh<cr><Plug>(jsdoc)
5353
```
54+
55+
## TypeScript
56+
Since ver 0.10.0 jsdoc.vim support TypeScript.
57+
58+
```typescript
59+
function foo(foo: string): string {
60+
return 'foo'
61+
}
62+
```
63+
64+
`:JsDoc` would generate following.
65+
66+
```typescript
67+
/**
68+
* foo
69+
*
70+
* @param {string} foo
71+
* @returns {string}
72+
*/
73+
function foo(foo: string): string {
74+
return 'foo'
75+
}
76+
```
77+
`param` and `returns` set `type` automatically.

autoload/jsdoc.vim

Lines changed: 167 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
" File: jsdoc.vim
22
" Author: NAKAMURA, Hisashi <https://github.com/sunvisor>
33
" Modifyed: Shinya Ohyanagi <[email protected]>
4-
" Version: 0.9.1
4+
" Version: 0.10.0
55
" WebPage: http://github.com/heavenshell/vim-jsdoc/
66
" Description: Generate JSDoc to your JavaScript file.
77
" License: BSD, see LICENSE for more details.
@@ -50,6 +50,10 @@ let g:jsdoc_tags = exists('g:jsdoc_tags')
5050
" Fill in any missing ones with defaults, keeping user overrides
5151
call extend(g:jsdoc_tags, s:jsdoc_default_tags, 'keep')
5252

53+
" Add param types when `g:jsdoc_input_description=1` and has typed parameter,
54+
" such as TypeScript.
55+
let s:candidate_type = ''
56+
5357
" Return data types for argument type auto completion :)
5458
function! jsdoc#listDataTypes(A, L, P) abort
5559
let l:types = [
@@ -71,6 +75,13 @@ function! jsdoc#listDataTypes(A, L, P) abort
7175
\ 'typedArray', 'TypedArray',
7276
\ 'weakmap', 'WeakMap',
7377
\ 'weakset', 'WeakSet']
78+
79+
if s:candidate_type != ''
80+
" Ignore Builtin types. Already defined at `l:types`.
81+
if index(l:types, s:candidate_type) == -1
82+
call insert(l:types, s:candidate_type, 0)
83+
endif
84+
endif
7485
return join(l:types, "\n")
7586
endfunction
7687

@@ -92,9 +103,46 @@ let s:regexs = {
92103
\ 'class': '^.\{-}\s*class\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\).*$',
93104
\ 'shorthand': '^.\{-}\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\)\s*(\s*\([^)]*\)\s*).*$',
94105
\ 'static': '^.\{-}\s*static\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\)\s*(\s*\([^)]*\)\s*).*$',
95-
\ 'arrow': '^.\{-}\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\)\s*[:=]\s*(\s*\([^)]*\)\s*)\s*=>.*$'
106+
\ 'arrow': '^.\{-}\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\)\s*[:=]\s*(\s*\([^)]*\)\s*)\s*=>.*$',
107+
\ 'return_type': ')\(:\|:\s\|\s*:\s*\)\([a-zA-Z]\+\).*$',
108+
\ 'interface': '^.\{-}\s*interface\s*\([a-zA-Z_$][a-zA-Z0-9_$]*\).*$',
109+
\ 'access': '^\(public\|protected\|private\)',
110+
\ 'implements': '^.\{-}\s*implements\s*\(\([^{]*\)\).*$',
111+
\ 'extends': '^.\{-}\s*extends\s*\([^\s*]\)'
96112
\ }
97113

114+
function! s:trim(value)
115+
return substitute(a:value, '\s', '', '')
116+
endfunction
117+
118+
" If someday Vim support lambda use lambda.
119+
function! s:parse_type(args)
120+
let results = []
121+
for arg in a:args
122+
if arg =~# ':'
123+
let args = split(arg, ':')
124+
let val = args[0]
125+
if val =~# s:regexs['access']
126+
"let val = substitute(split(val, s:regexs['access'])[0], '\s', '', '')
127+
let val = s:trim(split(val, s:regexs['access'])[0])
128+
endif
129+
130+
"let type = substitute(args[1], '\s', '', '')
131+
let type = s:trim(args[1])
132+
" Split keywaord args.
133+
if type =~# '='
134+
"let type = substitute(split(type, '=')[0], '\s', '', '')
135+
let type = s:trim(split(type, '=')[0])
136+
endif
137+
call add(results, {'val': val, 'type': type})
138+
else
139+
call add(results, {'val': arg, 'type': ''})
140+
endif
141+
endfor
142+
143+
return results
144+
endfunction
145+
98146
function! s:build_description(argType, arg) abort
99147
let l:description = ''
100148
let l:override = 0
@@ -113,7 +161,7 @@ function! s:build_description(argType, arg) abort
113161

114162
" Prompt for description
115163
if l:override == 0
116-
let l:inputDescription = input('Argument "' . a:arg . '" description: ')
164+
let l:inputDescription = input('Argument "' . a:arg['val'] . '" description: ')
117165
if l:inputDescription !=# ''
118166
let l:description = l:inputDescription
119167
endif
@@ -181,74 +229,121 @@ function! s:hookArgs(lines, space, arg, hook, argType, argDescription) abort
181229

182230
endfunction
183231

184-
function! jsdoc#insert() abort
185-
let l:line = getline('.')
186-
let l:indentCharSpace = ' '
187-
let l:indentCharTab = ' '
188-
let l:autoexpandtab = &l:expandtab
189-
190-
if l:autoexpandtab == 0 " noexpandtab
191-
" tabs
192-
let l:indent = indent('.') / &l:tabstop
193-
let l:indentChar = l:indentCharTab
194-
elseif l:autoexpandtab == 1 " expandtab
195-
" spaces
196-
let l:indent = indent('.')
197-
let l:indentChar = l:indentCharSpace
198-
endif
232+
function! s:determine_style(line)
233+
let l:is_class = 0
234+
let l:is_function = 0
235+
let l:is_named = 0
236+
let l:is_static = 0
237+
let l:is_interface = 0
238+
let l:regex = 0
199239

200-
let l:space = repeat(l:indentChar, l:indent)
201-
202-
" Determine function defintion style
203-
let l:is_class = 0
204-
let l:is_function = 0
205-
let l:is_named = 0
206-
let l:is_static = 0
207-
if l:line =~ s:regexs['function_declaration']
240+
if a:line =~ s:regexs['function_declaration']
208241
let l:is_function = 1
209242
let l:is_named = 1
210243
let l:regex = s:regexs['function_declaration']
211-
elseif l:line =~ s:regexs['function_expression']
244+
elseif a:line =~ s:regexs['function_expression']
212245
let l:is_function = 1
213246
let l:is_named = 1
214247
let l:regex = s:regexs['function_expression']
215-
elseif l:line =~ s:regexs['anonymous_function']
248+
elseif a:line =~ s:regexs['anonymous_function']
216249
let l:is_function = 1
217250
let l:regex = s:regexs['anonymous_function']
218-
elseif g:jsdoc_enable_es6 == 1 && l:line =~ s:regexs['static']
251+
elseif g:jsdoc_enable_es6 == 1 && a:line =~ s:regexs['static']
219252
let l:is_function = 1
220253
let l:is_named = 1
221254
let l:is_static = 1
222255
let l:regex = s:regexs['static']
223-
elseif (g:jsdoc_allow_shorthand == 1 || g:jsdoc_enable_es6 == 1) && l:line =~ s:regexs['shorthand']
224-
echomsg string('shorthand')
256+
elseif (g:jsdoc_allow_shorthand == 1 || g:jsdoc_enable_es6 == 1) && a:line =~ s:regexs['shorthand']
225257
let l:is_function = 1
226258
let l:is_named = 1
227259
let l:regex = s:regexs['shorthand']
228-
elseif g:jsdoc_enable_es6 == 1 && l:line =~ s:regexs['arrow']
260+
elseif g:jsdoc_enable_es6 == 1 && a:line =~ s:regexs['arrow']
229261
let l:is_function = 1
230262
let l:is_named = 1
231263
let l:regex = s:regexs['arrow']
232-
elseif g:jsdoc_enable_es6 == 1 && l:line =~ s:regexs['class_extend']
264+
elseif g:jsdoc_enable_es6 == 1 && a:line =~ s:regexs['class_extend']
233265
let l:is_class = 1
234266
let l:is_named = 1
235267
let l:regex = s:regexs['class_extend']
236-
elseif g:jsdoc_enable_es6 == 1 && l:line =~ s:regexs['class']
268+
elseif g:jsdoc_enable_es6 == 1 && a:line =~ s:regexs['class']
237269
let l:is_class = 1
238270
let l:is_named = 1
239271
let l:regex = s:regexs['class']
272+
elseif a:line =~ s:regexs['interface']
273+
let l:is_interface = 1
274+
let l:is_named = 1
275+
let l:regex = s:regexs['interface']
276+
endif
277+
278+
return {
279+
\ 'is_class': l:is_class,
280+
\ 'is_function': l:is_function,
281+
\ 'is_named': l:is_named,
282+
\ 'is_static': l:is_static,
283+
\ 'is_interface': l:is_interface,
284+
\ 'regex': l:regex,
285+
\ }
286+
endfunction
287+
288+
" Extract return type such as TypeScript.
289+
function! s:extract_return_type(line)
290+
let l:return_type = ''
291+
if a:line =~ s:regexs['return_type']
292+
let matched = matchstr(a:line, s:regexs['return_type'], 0)
293+
if matched != ''
294+
" FIXME:
295+
" If signature has union tyoe, such as `function foo(): string | number {`
296+
" Regex `\([a-zA-Z]\+\).*$` would extract like `number | string {`.
297+
" So delete `{` if exists.
298+
let l:return_type = matchstr(matched, '\([a-zA-Z]\+\).*$', 0)
299+
let l:return_type = substitute(l:return_type, '\(\s*{\|{\)', '', 'g')
300+
endif
240301
endif
241302

303+
return l:return_type
304+
endfunction
305+
306+
function! jsdoc#insert() abort
307+
let l:line = getline('.')
308+
let l:indentCharSpace = ' '
309+
let l:indentCharTab = ' '
310+
let l:autoexpandtab = &l:expandtab
311+
312+
if l:autoexpandtab == 0 " noexpandtab
313+
" tabs
314+
let l:indent = indent('.') / &l:tabstop
315+
let l:indentChar = l:indentCharTab
316+
elseif l:autoexpandtab == 1 " expandtab
317+
" spaces
318+
let l:indent = indent('.')
319+
let l:indentChar = l:indentCharSpace
320+
endif
321+
322+
let l:space = repeat(l:indentChar, l:indent)
323+
324+
" Determine function defintion style
325+
let l:style = s:determine_style(l:line)
326+
let l:is_class = l:style['is_class']
327+
let l:is_function = l:style['is_function']
328+
let l:is_named = l:style['is_named']
329+
let l:is_static = l:style['is_static']
330+
let l:is_interface = l:style['is_interface']
331+
let l:regex = l:style['regex']
332+
242333
let l:lines = []
243334
let l:desc = g:jsdoc_input_description == 1 ? input('Description: ') : ''
244335
call add(l:lines, l:space . '/**')
245336
call add(l:lines, l:space . ' * ' . l:desc)
246-
if !l:is_class
337+
" Class and interface generate only typed name.
338+
if !l:is_class && !l:is_interface
247339
call add(l:lines, l:space . ' *')
248340
endif
249341

250342
let l:funcName = ''
251-
if l:is_function || l:is_class
343+
let l:parent_class = ''
344+
let l:implements = ''
345+
let l:return_type = ''
346+
if l:is_function || l:is_class || l:is_interface
252347

253348
" Parse function definition
254349
" @FIXME: Does not work if function is split over several lines...
@@ -266,13 +361,23 @@ function! jsdoc#insert() abort
266361
let l:argString = substitute(l:line, l:regex, '\1', 'g')
267362
endif
268363
let l:args = []
269-
let l:parent_class = ''
270364
if l:is_class
271-
let l:parent_class = substitute(l:argString, '\s', '', '')
365+
"let l:parent_class = substitute(l:argString, '\s', '', '')
366+
if l:argString =~ s:regexs['implements']
367+
let implements = s:trim(substitute(l:argString, s:regexs['implements'], '\1', 'g'))
368+
endif
369+
let l:extends = substitute(l:argString, s:regexs['extends'], '\1', 'g')
370+
let l:parent_class = matchstr(l:extends, '^\([a-zA-Z0-9-_$]*\)')
272371
else
273372
let l:args = split(l:argString, '\s*,\s*')
274373
endif
275374

375+
" Return type for typed language such as TypeScript.
376+
let l:return_type = s:extract_return_type(l:line)
377+
378+
" Parse typed args. ex: TypeScript's argument.
379+
let l:args = s:parse_type(l:args)
380+
276381
if g:jsdoc_additional_descriptions == 1
277382
call add(l:lines, l:space . ' * @name ' . l:funcName)
278383
call add(l:lines, l:space . ' * @' . g:jsdoc_tags['function'])
@@ -298,39 +403,50 @@ function! jsdoc#insert() abort
298403

299404
let l:hook = keys(g:jsdoc_custom_args_hook)
300405
for l:arg in l:args
406+
let s:candidate_type = ''
301407
if g:jsdoc_enable_es6 == 1
302408
" Remove `(` or `)` from args.
303-
let l:arg = substitute(l:arg, '\((\|)\)', '', '')
409+
let l:arg['val'] = substitute(l:arg['val'], '\((\|)\)', '', '')
304410
endif
305411

306412
if g:jsdoc_allow_input_prompt == 1
307-
let l:argType = input('Argument "' . l:arg . '" type: ', '', 'custom,jsdoc#listDataTypes')
413+
if l:arg['type'] != ''
414+
let s:candidate_type = l:arg['type']
415+
endif
416+
let l:argType = input('Argument "' . l:arg['val'] . '" type: ', '', 'custom,jsdoc#listDataTypes')
417+
redraw | echo ''
308418
let l:argDescription = s:build_description(l:argType, l:arg)
309419
if g:jsdoc_custom_args_hook == {}
310420
" Prepend separator to start of description only if it was provided
311421
if l:argDescription !=# ''
312422
let l:argDescription = g:jsdoc_param_description_separator . l:argDescription
313423
endif
314-
call add(l:lines, l:space . ' * @' . g:jsdoc_tags['param'] . ' {' . l:argType . '} ' . l:arg . l:argDescription)
424+
call add(l:lines, l:space . ' * @' . g:jsdoc_tags['param'] . ' {' . l:argType . '} ' . l:arg['val'] . l:argDescription)
315425
else
316-
let l:lines = s:hookArgs(l:lines, l:space, l:arg, l:hook, l:argType, l:argDescription)
426+
let l:lines = s:hookArgs(l:lines, l:space, l:arg['val'], l:hook, l:argType, l:argDescription)
317427
endif
318428
else
319429
" Hook args.
320-
let l:lines = s:hookArgs(l:lines, l:space, l:arg, l:hook, '', '')
430+
let l:lines = s:hookArgs(l:lines, l:space, l:arg['val'], l:hook, l:arg['type'], '')
321431
endif
322432
endfor
323433
endif
324434

325-
if l:is_class
326-
" Class does not need return description.
435+
if l:is_class || l:is_interface
436+
" Class and inteface does not need return description.
327437
if l:parent_class != ''
328438
call add(l:lines, l:space . ' *')
329-
call add(l:lines, l:space . ' * @extends ' . l:parent_class)
439+
if l:implements != ''
440+
call add(l:lines, l:space . ' * @implements ' . '{' . l:implements . '}')
441+
endif
442+
443+
call add(l:lines, l:space . ' * @extends ' . '{' . l:parent_class . '}')
330444
endif
331445
elseif g:jsdoc_return == 1
332446
if g:jsdoc_allow_input_prompt == 1
447+
let s:candidate_type = l:return_type
333448
let l:returnType = input('Return type (blank for no @' . g:jsdoc_tags['returns'] . '): ', '', 'custom,jsdoc#listDataTypes')
449+
redraw | echo ''
334450
let l:returnDescription = ''
335451
if l:returnType !=# ''
336452
if g:jsdoc_return_description == 1
@@ -342,7 +458,11 @@ function! jsdoc#insert() abort
342458
call add(l:lines, l:space . ' * @' . g:jsdoc_tags['returns'] . ' {' . l:returnType . '}' . l:returnDescription)
343459
endif
344460
else
345-
call add(l:lines, l:space . ' * @' . g:jsdoc_tags['returns'] . ' {undefined}')
461+
let l:return_type = l:return_type == ''
462+
\ ? ' {undefined}'
463+
\ : printf(' {%s}', l:return_type)
464+
465+
cal add(l:lines, l:space . ' * @' . g:jsdoc_tags['returns'] . l:return_type)
346466
endif
347467
endif
348468
call add(l:lines, l:space . ' */')

0 commit comments

Comments
 (0)