8000 Merge better extraction of default param values. Close #57. · pyodide/sphinx-js@20fe8c5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 20fe8c5

Browse files
committed
Merge better extraction of default param values. Close #57.
2 parents 5899410 + 967dba6 commit 20fe8c5

File tree

7 files changed

+143
-38
lines changed

7 files changed

+143
-38
lines changed

README.rst

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,46 @@ Parameter properties and destructuring parameters also work fine, using `standar
104104
* Export an image from the given canvas and save it to the disk.
105105
*
106106
* @param {Object} options Output options
107-
* @param {string} options.format The output format (``jpeg``, ``png``, or ``webp``)
108-
* @param {number} options.quality The output quality when format is ``jpeg`` or ``webp`` (from ``0.00`` to ``1.00``)
107+
* @param {string} options.format The output format (``jpeg``, ``png``, or
108+
* ``webp``)
109+
* @param {number} options.quality The output quality when format is
110+
* ``jpeg`` or ``webp`` (from ``0.00`` to ``1.00``)
109111
*/
110112
function saveCanvas({ format, quality }) {
111113
// ...
112114
}
113115

116+
Extraction of default parameter values works as well. These act as expected, with a few caveats::
117+
118+
/**
119+
* You must declare the params, even if you have nothing else to say, so
120+
* JSDoc will extract the default values.
121+
*
122+
* @param [num]
123+
* @param [str]
124+
* @param [bool]
125+
* @param [nil]
126+
*/
127+
function defaultsDocumentedInCode(num=5, str="true", bool=true, nil=null) {}
128+
129+
/**
130+
* JSDoc guesses types for things like "42". If you have a string-typed
131+
* default value that looks like a number or boolean, you'll need to
132+
* specify its type explicitly. Conversely, if you have a more complex
133+
* value like an arrow function, specify a non-string type on it so it
134+
* isn't interpreted as a string. Finally, if you have a disjoint type like
135+
* {string|Array} specify string first if you want your default to be
136+
* interpreted as a string.
137+
*
138+
* @param {function} [func=() => 5]
139+
* @param [str=some string]
140+
* @param {string} [strNum=42]
141+
* @param {string|Array} [strBool=true]
142+
* @param [num=5]
143+
* @param [nil=null]
144+
*/
145+
function defaultsDocumentedInDoclet(func, strNum, strBool, num, nil) {}
146+
114147
You can even add additional content. If you do, it will appear just below any extracted documentation::
115148

116149
.. js:autofunction:: someFunction
@@ -283,7 +316,7 @@ Version History
283316
* Use documented ``@params`` to help fill out the formal param list for a
284317
function. This keeps us from missing params that use destructuring. (flozz)
285318
* Improve error reporting when jsdoc is missing.
286-
* Add extracted default values to generated formal param lists. (flozz)
319+
* Add extracted default values to generated formal param lists. (flozz and erikrose)
287320

288321
2.4
289322
* Support the ``@example`` tag. (lidavidm)

sphinx_js/renderers.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from collections import OrderedDict
2+
from json import dumps
23
from re import sub
34

45
from docutils.parsers.rst import Parser as RstParser
56
from docutils.statemachine import StringList
67
from docutils.utils import new_document
78
from jinja2 import Environment, PackageLoader
8-
from six import iteritems
9+
from six import iteritems, string_types
910
from sphinx.errors import SphinxError
1011
from sphinx.util import rst
1112

@@ -108,27 +109,81 @@ def _name(self):
108109

109110
def _formal_params(self, doclet):
110111
"""Return the JS function or class params, looking first to any
111-
explicit params written into the directive and falling back to
112-
those in the JS code."""
112+
explicit params written into the directive and falling back to those in
113+
the JS code.
114+
115+
Return a ReST-escaped string ready for substitution into the template.
116+
117+
"""
118+
def format_default_according_to_type_hints(value, declared_types):
119+
"""Return the default value for a param, formatted as a string
120+
ready to be used in a formal parameter list.
121+
122+
JSDoc is a mess at extracting default values. It can unambiguously
123+
extract only a few simple types from the function signature, and
124+
ambiguity is even more rife when extracting from doclets. So we use
125+
any declared types to resolve the ambiguity.
126+
127+
:arg value: The extracted value, which may be of the right or wrong type
128+
:arg declared_types: A list of types declared in the doclet for
129+
this param. For example ``{string|number}`` would yield ['string',
130+
'number'].
131+
132+
"""
133+
def first(list, default):
134+
try:
135+
return list[0]
136+
except IndexError:
137+
return default
138+
139+
declared_type_implies_string = first(declared_types, '') == 'string'
140+
141+
# If the first item of the type disjunction is "string", we treat
142+
# the default value as a string. Otherwise, we don't. So, if you
143+
# want your ambiguously documented default like ``@param
144+
# {string|Array} [foo=[]]`` to be treated as a string, make sure
145+
# "string" comes first.
146+
if isinstance(value, string_types): # JSDoc threw it to us as a string in the JSON.
147+
if declared_types and not declared_type_implies_string:
148+
# It's a spurious string, like ``() => 5`` or a variable name.
149+
# Let it through verbatim.
150+
return value
151+
else:
152+
# It's a real string.
153+
return dumps(value) # Escape any contained quotes.
154+
else: # It came in as a non-string.
155+
if declared_type_implies_string:
156+
# It came in as an int, null, or bool, and we have to
157+
# convert it back to a string.
158+
return '"%s"' % (dumps(value),)
159+
else:
160+
# It's fine as the type it is.
161+
return dumps(value)
162+
113163
if self._explicit_formal_params:
114164
return self._explicit_formal_params
115165

116166
# Harvest params from the @param tag unless they collide with an
117167
# explicit formal param. Even look at params that are really
118-
# documenting subproperties of formal params. Also handles params
119-
# default values.
168+
# documenting subproperties of formal params. Also handle param default
169+
# values.
120170
params = []
121171
used_names = []
172+
MARKER = object()
122173

123-
for name, default in [(param['name'].split('.')[0], param.get('defaultvalue'))
124-
for param in doclet.get('params', [])]:
174+
for name, default, type in [(param['name'].split('.')[0],
175+
param.get('defaultvalue', MARKER),
176+
param.get('type', {'names': []}))
177+
for param in doclet.get('params', [])]:
125178
if name not in used_names:
126-
params.append('%s=%s' % (name, default) if default is not None else name)
179+
params.append(rst.escape(name) if default is MARKER else
180+
'%s=%s' % (rst.escape(name),
181+
rst.escape(format_default_according_to_type_hints(default, type['names']))))
127182
used_names.append(name)
128183

129184
# Use params from JS code if there are no documented params:
130185
if not params:
131-
params = doclet['meta']['code'].get('paramnames', [])
186+
params = [rst.escape(p) for p in doclet['meta']['code'].get('paramnames', [])]
132187

133188
return '(%s)' % ', '.join(params)
134189

tests/test_build/source/code.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,27 @@ const ExampleAttribute = null;
150150
*/
151151
function destructuredParams(p1, {foo, bar}) {}
152152

153-
/**
154-
* @param {number} [p1=42]
155-
* @param {string} [p2]
156-
* @param {string} [p3="true"]
157-
*/
158-
function defaultValues(p1, p2="foobar", p3="true") {}
159-
160153
/**
161154
* @param a_
162155
* @param {type_} b
163156
* @returns {rtype_}
164157
*/
165158
function injection() {}
159+
160+
/**
161+
* @param {function} [func=() => 5]
162+
* @param [str=a string with " quote]
163+
* @param {string} [strNum=42]
164+
* @param {string} [strBool=true]
165+
* @param [num=5]
166+
* @param [nil=null]
167+
*/
168+
function defaultsDocumentedInDoclet(func, strNum, strBool, num, nil) {}
169+
170+
/**
171+
* @param [num]
172+
* @param [str]
173+
* @param [bool]
174+
* @param [nil]
175+
*/
176+
function defaultsDocumentedInCode(num=5, str="true", bool=true, nil=null) {}

tests/test_build/source/docs/autofunction_default_values.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. js:autofunction:: defaultsDocumentedInCode
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. js:autofunction:: defaultsDocumentedInDoclet

tests/test_build/test_build.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,26 +72,31 @@ def test_autofunction_destructured_params(self):
7272
' * **p2.foo** (*string*) --\n\n'
7373
' * **p2.bar** (*string*) --\n')
7474

75-
def test_autofunction_default_values(self):
76-
"""Make sure params default values appear in the function definition,
77-
whether the defaults are defined in JSDoc or JS.
78-
79-
Unfortunately, there is no way to disambiguate strings from symbolic
80-
values used as default args in JS code: for instance, ``true`` vs.
81-
``"true"``. JSDoc's doclets render them both without quotes. However,
82-
you can work around this by specifying your defaults in the JSDoc
83-
comment rather than the JS code, as we do with p3. It would be lovely
84-
if JSDoc changed its behavior someday so we could know to put quotes
85-
around "foobar" as well.
86-
87-
"""
75+
def test_autofunction_defaults_in_doclet(self):
76+
"""Make sure param default values appear in the function definition,
77+
when defined in JSDoc."""
8878
self._file_contents_eq(
89-
'autofunction_default_values',
90-
u'defaultValues(p1=42, p2=foobar, p3="true")\n\n'
79+
'autofunction_defaults_doclet',
80+
'defaultsDocumentedInDoclet(func=() => 5, str="a string with \\" quote", strNum="42", strBool="true", num=5, nil=null)\n\n'
9181
' Arguments:\n'
92-
' * **p1** (*number*) --\n\n'
93-
' * **p2** (*string*) --\n\n'
94-
' * **p3** (*string*) --\n')
82+
' * **func** (*function*) --\n\n'
83+
' * **str** --\n\n'
84+
' * **strNum** (*string*) --\n\n'
85+
' * **strBool** (*string*) --\n\n'
86+
' * **num** --\n\n'
87+
' * **nil** --\n')
88+
89+
def test_autofunction_defaults_in_code(self):
90+
"""Make sure param default values appear in the function definition,
91+
when defined in code."""
92+
self._file_contents_eq(
93+
'autofunction_defaults_code',
94+
'defaultsDocumentedInCode(num=5, str="true", bool=true, nil=null)\n\n'
95+
' Arguments:\n'
96+
' * **num** --\n\n'
97+
' * **str** --\n\n'
98+
' * **bool** --\n\n'
99+
' * **nil** --\n')
95100

96101
def test_autoclass(self):
97102
"""Make sure classes show their class comment and constructor

0 commit comments

Comments
 (0)
0