rapydscript-ns 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +19 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_GAPS.md +420 -0
  8. package/README.md +153 -29
  9. package/TODO.md +16 -118
  10. package/add-toc-to-readme +2 -2
  11. package/bin/export +75 -75
  12. package/bin/rapydscript +70 -70
  13. package/bin/web-repl-export +102 -102
  14. package/build +2 -2
  15. package/language-service/index.js +237 -8
  16. package/memory/project_string_impl.md +43 -0
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +248 -38
  20. package/release/baselib-plain-ugly.js +8 -8
  21. package/release/compiler.js +778 -277
  22. package/release/signatures.json +30 -30
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +4 -1
  26. package/src/baselib-builtins.pyj +56 -2
  27. package/src/baselib-containers.pyj +2 -0
  28. package/src/baselib-errors.pyj +7 -3
  29. package/src/baselib-internal.pyj +51 -6
  30. package/src/baselib-str.pyj +5 -3
  31. package/src/compiler.pyj +36 -36
  32. package/src/errors.pyj +30 -30
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/asyncio.pyj +534 -0
  35. package/src/lib/base64.pyj +399 -0
  36. package/src/lib/bisect.pyj +73 -0
  37. package/src/lib/collections.pyj +1 -1
  38. package/src/lib/copy.pyj +120 -120
  39. package/src/lib/csv.pyj +494 -0
  40. package/src/lib/elementmaker.pyj +83 -83
  41. package/src/lib/encodings.pyj +126 -126
  42. package/src/lib/gettext.pyj +569 -569
  43. package/src/lib/heapq.pyj +98 -0
  44. package/src/lib/html.pyj +382 -0
  45. package/src/lib/http/__init__.pyj +98 -0
  46. package/src/lib/http/client.pyj +304 -0
  47. package/src/lib/http/cookies.pyj +236 -0
  48. package/src/lib/itertools.pyj +580 -580
  49. package/src/lib/logging.pyj +672 -0
  50. package/src/lib/math.pyj +193 -193
  51. package/src/lib/operator.pyj +11 -11
  52. package/src/lib/pythonize.pyj +20 -20
  53. package/src/lib/random.pyj +118 -118
  54. package/src/lib/react.pyj +74 -74
  55. package/src/lib/string.pyj +357 -0
  56. package/src/lib/textwrap.pyj +329 -0
  57. package/src/lib/traceback.pyj +63 -63
  58. package/src/lib/urllib/__init__.pyj +14 -0
  59. package/src/lib/urllib/error.pyj +66 -0
  60. package/src/lib/urllib/parse.pyj +475 -0
  61. package/src/lib/urllib/request.pyj +86 -0
  62. package/src/lib/uuid.pyj +77 -77
  63. package/src/monaco-language-service/analyzer.js +5 -2
  64. package/src/monaco-language-service/completions.js +26 -0
  65. package/src/monaco-language-service/diagnostics.js +202 -3
  66. package/src/monaco-language-service/dts.js +550 -550
  67. package/src/monaco-language-service/scope.js +1 -0
  68. package/src/output/comments.pyj +45 -45
  69. package/src/output/exceptions.pyj +201 -201
  70. package/src/output/functions.pyj +152 -6
  71. package/src/output/jsx.pyj +164 -164
  72. package/src/output/loops.pyj +17 -2
  73. package/src/output/modules.pyj +1 -1
  74. package/src/output/operators.pyj +15 -0
  75. package/src/output/stream.pyj +0 -1
  76. package/src/output/treeshake.pyj +182 -182
  77. package/src/output/utils.pyj +72 -72
  78. package/src/parse.pyj +80 -17
  79. package/src/string_interpolation.pyj +72 -72
  80. package/src/tokenizer.pyj +1 -1
  81. package/src/unicode_aliases.pyj +576 -576
  82. package/src/utils.pyj +192 -192
  83. package/test/_import_one.pyj +37 -37
  84. package/test/_import_two/__init__.pyj +11 -11
  85. package/test/_import_two/level2/deep.pyj +4 -4
  86. package/test/_import_two/other.pyj +6 -6
  87. package/test/_import_two/sub.pyj +13 -13
  88. package/test/aes_vectors.pyj +421 -421
  89. package/test/annotations.pyj +80 -80
  90. package/test/async_generators.pyj +144 -0
  91. package/test/asyncio.pyj +307 -0
  92. package/test/base64.pyj +202 -0
  93. package/test/bisect.pyj +178 -0
  94. package/test/csv.pyj +405 -0
  95. package/test/decorators.pyj +77 -77
  96. package/test/docstrings.pyj +39 -39
  97. package/test/elementmaker_test.pyj +45 -45
  98. package/test/float_special.pyj +64 -0
  99. package/test/functions.pyj +151 -151
  100. package/test/generators.pyj +41 -41
  101. package/test/generic.pyj +370 -370
  102. package/test/heapq.pyj +174 -0
  103. package/test/html.pyj +212 -0
  104. package/test/http.pyj +259 -0
  105. package/test/imports.pyj +79 -72
  106. package/test/internationalization.pyj +73 -73
  107. package/test/lint.pyj +164 -164
  108. package/test/logging.pyj +356 -0
  109. package/test/long.pyj +130 -0
  110. package/test/loops.pyj +85 -85
  111. package/test/numpy.pyj +734 -734
  112. package/test/parenthesized_with.pyj +141 -0
  113. package/test/python_compat.pyj +3 -5
  114. package/test/python_modulo.pyj +76 -0
  115. package/test/python_modulo_off.pyj +21 -0
  116. package/test/repl.pyj +121 -121
  117. package/test/scoped_flags.pyj +76 -76
  118. package/test/str.pyj +14 -0
  119. package/test/string.pyj +245 -0
  120. package/test/textwrap.pyj +172 -0
  121. package/test/type_display.pyj +48 -0
  122. package/test/type_enforcement.pyj +164 -0
  123. package/test/unit/index.js +14 -6
  124. package/test/unit/language-service-completions.js +119 -0
  125. package/test/unit/language-service-dts.js +543 -543
  126. package/test/unit/language-service-hover.js +455 -455
  127. package/test/unit/language-service-scope.js +32 -0
  128. package/test/unit/language-service.js +127 -3
  129. package/test/unit/run-language-service.js +17 -3
  130. package/test/unit/web-repl.js +2094 -29
  131. package/test/urllib.pyj +193 -0
  132. package/tools/compile.js +1 -1
  133. package/tools/compiler.d.ts +367 -367
  134. package/tools/completer.js +131 -131
  135. package/tools/embedded_compiler.js +7 -7
  136. package/tools/gettext.js +185 -185
  137. package/tools/ini.js +65 -65
  138. package/tools/msgfmt.js +187 -187
  139. package/tools/repl.js +223 -223
  140. package/tools/test.js +118 -118
  141. package/tools/utils.js +128 -128
  142. package/tools/web_repl.js +95 -95
  143. package/try +41 -41
  144. package/web-repl/env.js +196 -196
  145. package/web-repl/index.html +163 -163
  146. package/web-repl/main.js +1 -1
  147. package/web-repl/prism.css +139 -139
  148. package/web-repl/prism.js +113 -113
  149. package/web-repl/rapydscript.js +224 -224
  150. package/web-repl/sha1.js +25 -25
  151. package/test/omit_function_metadata.pyj +0 -20
@@ -45,11 +45,69 @@ def function_args(argnames, output, strip_first):
45
45
 
46
46
  def function_preamble(node, output, offset):
47
47
  a = node.argnames
48
- if a is None or a is undefined or a.is_simple_func:
48
+ if a is None or a is undefined:
49
+ return
50
+ type_enforce = node.type_enforce
51
+ if a.is_simple_func and not type_enforce:
49
52
  return
50
53
  # If this function has optional parameters/*args/**kwargs declare it differently
51
54
  fname = node.name.name if node.name else anonfunc
52
55
  kw = 'arguments[arguments.length-1]'
56
+
57
+ # === Type enforcement: max positional-arg count check ===
58
+ if type_enforce:
59
+ max_pos = 0
60
+ for c, arg in enumerate(a):
61
+ if c >= offset and not arg.kwonly:
62
+ max_pos += 1
63
+ if a.is_simple_func:
64
+ # Simple function: named JS params; arguments.length gives true count
65
+ if not a.starargs:
66
+ # Strip a trailing kwargs-marker object so a `*args, **kwargs`
67
+ # caller does not get blamed for an extra positional argument
68
+ # (matches the complex-func variant below).
69
+ output.indent()
70
+ output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
71
+ output.end_statement()
72
+ output.indent()
73
+ output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
74
+ output.end_statement()
75
+ max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
76
+ output.indent()
77
+ output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
78
+ output.end_statement()
79
+ # Required-arg and type-annotation checks (variables are direct JS params)
80
+ for c, arg in enumerate(a):
81
+ if c < offset:
82
+ continue
83
+ if not arg.kwonly:
84
+ miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
85
+ output.indent()
86
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
87
+ output.end_statement()
88
+ if arg.annotation:
89
+ annot_name = arg.annotation.name or ''
90
+ type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
91
+ output.indent()
92
+ output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
93
+ arg.annotation.print(output)
94
+ output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
95
+ output.end_statement()
96
+ return # simple func: only enforcement, no complex preamble
97
+
98
+ # Complex function: must handle arguments object; strip trailing kwargs marker
99
+ if not a.starargs:
100
+ output.indent()
101
+ output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
102
+ output.end_statement()
103
+ output.indent()
104
+ output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
105
+ output.end_statement()
106
+ max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
107
+ output.indent()
108
+ output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
109
+ output.end_statement()
110
+
53
111
  # Define all formal parameters
54
112
  positional_count = 0
55
113
  for c, arg in enumerate(a):
@@ -89,6 +147,22 @@ def function_preamble(node, output, offset):
89
147
  output.indent()
90
148
  output.spaced('if', '(' + kw, '===', 'null', '||', 'typeof', kw, '!==', '"object"', '||', kw, '[ρσ_kwargs_symbol]', '!==', 'true)', kw, '=', '{}')
91
149
  output.end_statement()
150
+ # === Type enforcement: positional-only args must not appear in kwargs ===
151
+ if type_enforce:
152
+ posonly_count = a.posonly_count or 0
153
+ for pi in range(posonly_count):
154
+ if pi < offset:
155
+ continue
156
+ parg = a[pi]
157
+ posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + parg.name + '\''
158
+ output.indent()
159
+ output.spaced('if', '(Object.prototype.hasOwnProperty.call(' + kw + ',', '"' + parg.name + '"))')
160
+ output.with_block(def():
161
+ output.indent()
162
+ output.print('throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
163
+ output.end_statement()
164
+ )
165
+ output.newline()
92
166
  # Read values from the kwargs object for non-positional-only formal parameters with defaults
93
167
  if a.has_defaults:
94
168
  for dname in Object.keys(a.defaults):
@@ -138,6 +212,29 @@ def function_preamble(node, output, offset):
138
212
  output.end_statement()
139
213
  )
140
214
  output.newline()
215
+ elif type_enforce and (a.posonly_count or 0) > 0:
216
+ # No kwargs block, but function has positional-only params.
217
+ # A caller using foo(posonly_name=val) still passes a kwargs object as the
218
+ # last argument; detect and reject it here.
219
+ posonly_count = a.posonly_count or 0
220
+ posonly_names = []
221
+ for pi in range(posonly_count):
222
+ if pi >= offset:
223
+ posonly_names.push(a[pi].name)
224
+ if posonly_names.length > 0:
225
+ output.indent()
226
+ output.spaced('var', 'ρσ_kw_chk', '=', 'arguments[arguments.length-1]')
227
+ output.end_statement()
228
+ output.indent()
229
+ output.spaced('if', '(ρσ_kw_chk !== null && typeof ρσ_kw_chk === "object" && ρσ_kw_chk[ρσ_kwargs_symbol] === true)')
230
+ output.with_block(def():
231
+ for pname in posonly_names:
232
+ posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + pname + '\''
233
+ output.indent()
234
+ output.print('if (Object.prototype.hasOwnProperty.call(ρσ_kw_chk, "' + pname + '")) throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
235
+ output.end_statement()
236
+ )
237
+ output.newline()
141
238
 
142
239
  if a.starargs is not undefined:
143
240
  # Define the *args parameter, putting in whatever is left after assigning the formal parameters and the options object
@@ -160,6 +257,44 @@ def function_preamble(node, output, offset):
160
257
  output.spaced(kw, '=', 'ρσ_kwargs_to_dict(' + kw + ')')
161
258
  output.end_statement()
162
259
 
260
+ # === Type enforcement: required-arg, required-kwonly, and type-annotation checks ===
261
+ # These run after all variable resolution (kwargs reading, starargs setup, dict conversion)
262
+ # so each variable holds its final value when checked.
263
+ if type_enforce:
264
+ # Required positional args (no default, not kwonly)
265
+ for c, arg in enumerate(a):
266
+ if c < offset:
267
+ continue
268
+ if arg.kwonly:
269
+ continue
270
+ if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
271
+ miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
272
+ output.indent()
273
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
274
+ output.end_statement()
275
+ # Required keyword-only args (no default)
276
+ for c, arg in enumerate(a):
277
+ if not arg.kwonly:
278
+ continue
279
+ if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
280
+ miss_kw = fname + '() missing required keyword-only argument: \'' + arg.name + '\''
281
+ output.indent()
282
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_kw) + ')')
283
+ output.end_statement()
284
+ # Type annotation checks (skip *args and **kwargs)
285
+ for c, arg in enumerate(a):
286
+ if c < offset:
287
+ continue
288
+ if not arg.annotation:
289
+ continue
290
+ annot_name = arg.annotation.name or ''
291
+ type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
292
+ output.indent()
293
+ output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
294
+ arg.annotation.print(output)
295
+ output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
296
+ output.end_statement()
297
+
163
298
  def has_annotations(self):
164
299
  if self.return_annotation:
165
300
  return True
@@ -169,8 +304,6 @@ def has_annotations(self):
169
304
  return False
170
305
 
171
306
  def function_annotation(self, output, strip_first, name):
172
- if output.options.omit_function_metadata:
173
- return
174
307
  fname = name or (self.name.name if self.name else anonfunc)
175
308
  props = Object.create(None)
176
309
 
@@ -263,27 +396,37 @@ def function_definition(self, output, strip_first, as_expression):
263
396
  output.set_indentation(output.next_indent())
264
397
  output.spaced('(function()', '{'), output.newline()
265
398
  output.indent(), output.spaced('var', anonfunc, '='), output.space()
266
- if self.is_async:
399
+ # Async generators (async def with yield) are emitted as a sync wrapper that
400
+ # returns the async iterator directly (matching Python semantics, where calling
401
+ # an async generator function returns the iterator immediately without await).
402
+ # The inner js_generator is `async function*`. The outer wrapper is sync.
403
+ if self.is_async and not self.is_generator:
267
404
  output.print("async"), output.space()
268
405
  output.print("function"), output.space()
269
406
  if self.name:
270
407
  self.name.print(output)
271
408
 
272
409
  if self.is_generator:
410
+ inner_decl = 'async function* js_generator' if self.is_async else 'function* js_generator'
273
411
  output.print('()'), output.space()
274
412
  output.with_block(def():
275
413
  output.indent()
276
- output.print('function* js_generator')
414
+ output.print(inner_decl)
277
415
  function_args(self.argnames, output, strip_first)
278
416
  print_bracketed(self, output, True, function_preamble)
279
417
  output.newline()
280
418
  output.indent()
281
419
  output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
282
420
  output.end_statement()
283
- # Python's generator objects use a separate method to send data to the generator
421
+ # Python's generator objects use a separate method to send data to the generator.
422
+ # For async generators, asend() is the async equivalent — alias it to next as well.
284
423
  output.indent()
285
424
  output.spaced('result.send', '=', 'result.next')
286
425
  output.end_statement()
426
+ if self.is_async:
427
+ output.indent()
428
+ output.spaced('result.asend', '=', 'result.next')
429
+ output.end_statement()
287
430
  output.indent()
288
431
  output.spaced('return', 'result')
289
432
  output.end_statement()
@@ -443,12 +586,15 @@ def print_function_call(self, output):
443
586
  )
444
587
  elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
445
588
  # Bare print(...) → console.log(...) to avoid clobbering window.print
589
+ # Wrap each arg in ρσ_str() so type() and other objects display like Python
446
590
  output.print('console.log')
447
591
  output.with_parens(def():
448
592
  for i, a in enumerate(self.args):
449
593
  if i:
450
594
  output.comma()
595
+ output.print('ρσ_str(')
451
596
  a.print(output)
597
+ output.print(')')
452
598
  )
453
599
  elif (is_node_type(self.expression, AST_SymbolRef) and
454
600
  self.expression.name is 'vars' and self.args.length is 0):
@@ -1,164 +1,164 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- from __python__ import hash_literals
4
-
5
- from ast import AST_String, AST_JSXText, AST_JSXSpread, AST_JSXExprContainer, is_node_type
6
-
7
- def _is_component_tag(tag):
8
- # Components start with uppercase or use dot notation (e.g. Router.Route)
9
- first = tag[0]
10
- return (first >= 'A' and first <= 'Z') or '.' in tag
11
-
12
- def _needs_quoting(name):
13
- # Attribute names containing non-identifier chars (e.g. aria-label) need quoting
14
- return not v'/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)'
15
-
16
- def _decode_html_entities(text):
17
- # Decode HTML entities in a single pass to avoid double-decoding (e.g. &amp;lt; -> &lt;)
18
- return text.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, def(match, hex, dec, name):
19
- if hex:
20
- return String.fromCharCode(parseInt(hex, 16))
21
- if dec:
22
- return String.fromCharCode(parseInt(dec, 10))
23
- if name is 'amp':
24
- return '&'
25
- if name is 'lt':
26
- return '<'
27
- if name is 'gt':
28
- return '>'
29
- if name is 'quot':
30
- return '"'
31
- if name is 'apos':
32
- return "'"
33
- if name is 'nbsp':
34
- return '\u00a0'
35
- return match
36
- )
37
-
38
- def _normalize_jsx_whitespace(text):
39
- # Implements the Babel JSX whitespace algorithm:
40
- # - Split by newlines; trim leading whitespace from all lines except the first,
41
- # trailing whitespace from all lines except the last.
42
- # - Lines that are empty after trimming are dropped.
43
- # - Remaining non-empty lines are joined; each line except the last non-empty
44
- # gets a trailing space to separate it from the next.
45
- lines = text.split('\n')
46
- last_non_empty = -1
47
- for i in range(lines.length):
48
- if /[^ \t]/.test(lines[i]):
49
- last_non_empty = i
50
- result = ''
51
- for i in range(lines.length):
52
- line = lines[i].replace(/\t/g, ' ')
53
- is_first = (i is 0)
54
- is_last = (i is lines.length - 1)
55
- if not is_first:
56
- line = line.replace(/^[ ]+/, '')
57
- if not is_last:
58
- line = line.replace(/[ ]+$/, '')
59
- if line:
60
- if i is not last_non_empty:
61
- line += ' '
62
- result += line
63
- return result
64
-
65
- def _process_jsx_text(text):
66
- text = _normalize_jsx_whitespace(text)
67
- if text:
68
- text = _decode_html_entities(text)
69
- return text
70
-
71
- def _print_tag(tag, output):
72
- if _is_component_tag(tag):
73
- output.print(tag)
74
- else:
75
- output.print('"')
76
- output.print(tag)
77
- output.print('"')
78
-
79
- def _print_props(props, output):
80
- if not props or not props.length:
81
- output.print('null')
82
- return
83
- output.print('{')
84
- first = True
85
- for prop in props:
86
- if not first:
87
- output.print(', ')
88
- first = False
89
- if is_node_type(prop, AST_JSXSpread):
90
- output.print('...')
91
- prop.expression.print(output)
92
- else:
93
- if _needs_quoting(prop.name):
94
- output.print('"')
95
- output.print(prop.name)
96
- output.print('"')
97
- else:
98
- output.print(prop.name)
99
- output.print(': ')
100
- if prop.value is None:
101
- output.print('true')
102
- elif is_node_type(prop.value, AST_String):
103
- output.print_string(prop.value.value)
104
- else:
105
- prop.value.print(output)
106
- output.print('}')
107
-
108
- def _print_children(children, output):
109
- for child in children:
110
- if is_node_type(child, AST_JSXText):
111
- text = _process_jsx_text(child.value)
112
- if text:
113
- output.print(', ')
114
- output.print_string(text)
115
- elif is_node_type(child, AST_JSXExprContainer):
116
- output.print(', ')
117
- child.expression.print(output)
118
- else:
119
- output.print(', ')
120
- child.print(output)
121
-
122
- def print_jsx_element(self, output):
123
- output.print('React.createElement(')
124
- _print_tag(self.tag, output)
125
- output.print(', ')
126
- _print_props(self.props, output)
127
- if not self.self_closing:
128
- _print_children(self.children, output)
129
- output.print(')')
130
-
131
- def print_jsx_fragment(self, output):
132
- output.print('React.createElement(React.Fragment, null')
133
- _print_children(self.children, output)
134
- output.print(')')
135
-
136
- def print_jsx_attribute(self, output):
137
- # Handled directly by _print_props; kept for completeness
138
- if _needs_quoting(self.name):
139
- output.print('"')
140
- output.print(self.name)
141
- output.print('"')
142
- else:
143
- output.print(self.name)
144
- if self.value is not None:
145
- output.print(': ')
146
- if is_node_type(self.value, AST_String):
147
- output.print_string(self.value.value)
148
- else:
149
- self.value.print(output)
150
-
151
- def print_jsx_spread(self, output):
152
- # Handled directly by _print_props; kept for completeness
153
- output.print('...')
154
- self.expression.print(output)
155
-
156
- def print_jsx_text(self, output):
157
- # Handled directly by _print_children; kept for completeness
158
- text = _process_jsx_text(self.value)
159
- if text:
160
- output.print_string(text)
161
-
162
- def print_jsx_expr_container(self, output):
163
- # Handled directly by _print_children; kept for completeness
164
- self.expression.print(output)
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
+ from __python__ import hash_literals
4
+
5
+ from ast import AST_String, AST_JSXText, AST_JSXSpread, AST_JSXExprContainer, is_node_type
6
+
7
+ def _is_component_tag(tag):
8
+ # Components start with uppercase or use dot notation (e.g. Router.Route)
9
+ first = tag[0]
10
+ return (first >= 'A' and first <= 'Z') or '.' in tag
11
+
12
+ def _needs_quoting(name):
13
+ # Attribute names containing non-identifier chars (e.g. aria-label) need quoting
14
+ return not v'/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)'
15
+
16
+ def _decode_html_entities(text):
17
+ # Decode HTML entities in a single pass to avoid double-decoding (e.g. &amp;lt; -> &lt;)
18
+ return text.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, def(match, hex, dec, name):
19
+ if hex:
20
+ return String.fromCharCode(parseInt(hex, 16))
21
+ if dec:
22
+ return String.fromCharCode(parseInt(dec, 10))
23
+ if name is 'amp':
24
+ return '&'
25
+ if name is 'lt':
26
+ return '<'
27
+ if name is 'gt':
28
+ return '>'
29
+ if name is 'quot':
30
+ return '"'
31
+ if name is 'apos':
32
+ return "'"
33
+ if name is 'nbsp':
34
+ return '\u00a0'
35
+ return match
36
+ )
37
+
38
+ def _normalize_jsx_whitespace(text):
39
+ # Implements the Babel JSX whitespace algorithm:
40
+ # - Split by newlines; trim leading whitespace from all lines except the first,
41
+ # trailing whitespace from all lines except the last.
42
+ # - Lines that are empty after trimming are dropped.
43
+ # - Remaining non-empty lines are joined; each line except the last non-empty
44
+ # gets a trailing space to separate it from the next.
45
+ lines = text.split('\n')
46
+ last_non_empty = -1
47
+ for i in range(lines.length):
48
+ if /[^ \t]/.test(lines[i]):
49
+ last_non_empty = i
50
+ result = ''
51
+ for i in range(lines.length):
52
+ line = lines[i].replace(/\t/g, ' ')
53
+ is_first = (i is 0)
54
+ is_last = (i is lines.length - 1)
55
+ if not is_first:
56
+ line = line.replace(/^[ ]+/, '')
57
+ if not is_last:
58
+ line = line.replace(/[ ]+$/, '')
59
+ if line:
60
+ if i is not last_non_empty:
61
+ line += ' '
62
+ result += line
63
+ return result
64
+
65
+ def _process_jsx_text(text):
66
+ text = _normalize_jsx_whitespace(text)
67
+ if text:
68
+ text = _decode_html_entities(text)
69
+ return text
70
+
71
+ def _print_tag(tag, output):
72
+ if _is_component_tag(tag):
73
+ output.print(tag)
74
+ else:
75
+ output.print('"')
76
+ output.print(tag)
77
+ output.print('"')
78
+
79
+ def _print_props(props, output):
80
+ if not props or not props.length:
81
+ output.print('null')
82
+ return
83
+ output.print('{')
84
+ first = True
85
+ for prop in props:
86
+ if not first:
87
+ output.print(', ')
88
+ first = False
89
+ if is_node_type(prop, AST_JSXSpread):
90
+ output.print('...')
91
+ prop.expression.print(output)
92
+ else:
93
+ if _needs_quoting(prop.name):
94
+ output.print('"')
95
+ output.print(prop.name)
96
+ output.print('"')
97
+ else:
98
+ output.print(prop.name)
99
+ output.print(': ')
100
+ if prop.value is None:
101
+ output.print('true')
102
+ elif is_node_type(prop.value, AST_String):
103
+ output.print_string(prop.value.value)
104
+ else:
105
+ prop.value.print(output)
106
+ output.print('}')
107
+
108
+ def _print_children(children, output):
109
+ for child in children:
110
+ if is_node_type(child, AST_JSXText):
111
+ text = _process_jsx_text(child.value)
112
+ if text:
113
+ output.print(', ')
114
+ output.print_string(text)
115
+ elif is_node_type(child, AST_JSXExprContainer):
116
+ output.print(', ')
117
+ child.expression.print(output)
118
+ else:
119
+ output.print(', ')
120
+ child.print(output)
121
+
122
+ def print_jsx_element(self, output):
123
+ output.print('React.createElement(')
124
+ _print_tag(self.tag, output)
125
+ output.print(', ')
126
+ _print_props(self.props, output)
127
+ if not self.self_closing:
128
+ _print_children(self.children, output)
129
+ output.print(')')
130
+
131
+ def print_jsx_fragment(self, output):
132
+ output.print('React.createElement(React.Fragment, null')
133
+ _print_children(self.children, output)
134
+ output.print(')')
135
+
136
+ def print_jsx_attribute(self, output):
137
+ # Handled directly by _print_props; kept for completeness
138
+ if _needs_quoting(self.name):
139
+ output.print('"')
140
+ output.print(self.name)
141
+ output.print('"')
142
+ else:
143
+ output.print(self.name)
144
+ if self.value is not None:
145
+ output.print(': ')
146
+ if is_node_type(self.value, AST_String):
147
+ output.print_string(self.value.value)
148
+ else:
149
+ self.value.print(output)
150
+
151
+ def print_jsx_spread(self, output):
152
+ # Handled directly by _print_props; kept for completeness
153
+ output.print('...')
154
+ self.expression.print(output)
155
+
156
+ def print_jsx_text(self, output):
157
+ # Handled directly by _print_children; kept for completeness
158
+ text = _process_jsx_text(self.value)
159
+ if text:
160
+ output.print_string(text)
161
+
162
+ def print_jsx_expr_container(self, output):
163
+ # Handled directly by _print_children; kept for completeness
164
+ self.expression.print(output)
@@ -119,7 +119,10 @@ def print_for_loop_body(output):
119
119
  if not (self.simple_for_index or is_simple_for_in(self)):
120
120
  # if we're using multiple iterators, unpack them
121
121
  output.indent()
122
- if output.options.js_version is 5:
122
+ # `async for` always emits ES6+ `for await ... of` syntax (regardless
123
+ # of js_version), so the loop variable holds the value directly —
124
+ # not an integer index into an array.
125
+ if output.options.js_version is 5 and not self.is_async:
123
126
  itervar = "ρσ_Iter" + output.index_counter + "[ρσ_Index" + output.index_counter + "]"
124
127
  else:
125
128
  itervar = "ρσ_Index" + output.index_counter
@@ -237,7 +240,19 @@ def print_for_in(self, output):
237
240
  )
238
241
  else:
239
242
  # regular loop
240
- if output.options.js_version is 5:
243
+ if self.is_async:
244
+ # async for: emit native `for await ... of` regardless of js_version.
245
+ # Async iteration is an ES2018 feature; if the user wrote `async for`
246
+ # they have already opted into a modern runtime. The ρσ_Iterable
247
+ # bridge is skipped because async iterators don't expose .length
248
+ # or Map keys — the JS engine handles Symbol.asyncIterator natively.
249
+ itervar = "ρσ_Iter" + output.index_counter
250
+ output.assign("var " + itervar)
251
+ write_object()
252
+ output.end_statement()
253
+ output.indent()
254
+ output.spaced('for', 'await', '(var', 'ρσ_Index' + output.index_counter, 'of', itervar + ')')
255
+ elif output.options.js_version is 5:
241
256
  output.assign("var ρσ_Iter" + output.index_counter)
242
257
  output.print("ρσ_Iterable")
243
258
  output.with_parens(write_object)
@@ -94,7 +94,7 @@ def declare_exports(module_id, exports, output, docstrings):
94
94
  def _inject_pythonize_strings(output):
95
95
  str_funcs = ('capitalize strip lstrip rstrip islower isupper isspace lower upper swapcase title'
96
96
  ' center count endswith startswith find rfind index rindex format join ljust rjust'
97
- ' partition rpartition splitlines zfill').split(' ')
97
+ ' partition rpartition replace splitlines zfill').split(' ')
98
98
  output.newline()
99
99
  output.indent()
100
100
  output.print('(function(){var _f=' + JSON.stringify(str_funcs) + ';for(var _i=0;_i<_f.length;_i++)String.prototype[_f[_i]]=\u03c1\u03c3_str.prototype[_f[_i]];})()')
@@ -321,6 +321,13 @@ def print_binary_op(self, output):
321
321
  self.right.print(output)
322
322
  output.print(')')
323
323
  return
324
+ if self.operator is '%' and self.python_mod:
325
+ output.print('ρσ_op_mod_ns(')
326
+ self.left.print(output)
327
+ output.comma()
328
+ self.right.print(output)
329
+ output.print(')')
330
+ return
324
331
  if function_ops[self.operator]:
325
332
  output.print(function_ops[self.operator])
326
333
  output.with_parens(def():
@@ -485,6 +492,14 @@ def print_assign(self, output):
485
492
  self.right.print(output)
486
493
  )
487
494
  return
495
+ if self.operator is '%=' and self.python_mod:
496
+ output.assign(self.left)
497
+ output.print('ρσ_op_mod_ns(')
498
+ self.left.print(output)
499
+ output.comma()
500
+ self.right.print(output)
501
+ output.print(')')
502
+ return
488
503
  if self.operator is '**=':
489
504
  output.assign(self.left)
490
505
  if output.options.js_version > 6:
@@ -54,7 +54,6 @@ output_stream_defaults = {
54
54
  'module_cache_dir': '',
55
55
  'js_version':5,
56
56
  'write_name': True,
57
- 'omit_function_metadata': False,
58
57
  'pythonize_strings': False,
59
58
  'repl_mode': False,
60
59
  }