@ohos-ports/curlconverter 4.12.0-beta.0

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 (270) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/src/Headers.d.ts +23 -0
  4. package/dist/src/Headers.js +287 -0
  5. package/dist/src/Headers.js.map +1 -0
  6. package/dist/src/Query.d.ts +8 -0
  7. package/dist/src/Query.js +136 -0
  8. package/dist/src/Query.js.map +1 -0
  9. package/dist/src/Request.d.ts +200 -0
  10. package/dist/src/Request.js +1191 -0
  11. package/dist/src/Request.js.map +1 -0
  12. package/dist/src/Warnings.d.ts +15 -0
  13. package/dist/src/Warnings.js +111 -0
  14. package/dist/src/Warnings.js.map +1 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.js +333 -0
  17. package/dist/src/cli.js.map +1 -0
  18. package/dist/src/curl/auth.d.ts +12 -0
  19. package/dist/src/curl/auth.js +40 -0
  20. package/dist/src/curl/auth.js.map +1 -0
  21. package/dist/src/curl/form.d.ts +28 -0
  22. package/dist/src/curl/form.js +218 -0
  23. package/dist/src/curl/form.js.map +1 -0
  24. package/dist/src/curl/opts.d.ts +2052 -0
  25. package/dist/src/curl/opts.js +1000 -0
  26. package/dist/src/curl/opts.js.map +1 -0
  27. package/dist/src/curl/url.d.ts +14 -0
  28. package/dist/src/curl/url.js +218 -0
  29. package/dist/src/curl/url.js.map +1 -0
  30. package/dist/src/generators/ansible.d.ts +5 -0
  31. package/dist/src/generators/ansible.js +282 -0
  32. package/dist/src/generators/ansible.js.map +1 -0
  33. package/dist/src/generators/c.d.ts +12 -0
  34. package/dist/src/generators/c.js +1230 -0
  35. package/dist/src/generators/c.js.map +1 -0
  36. package/dist/src/generators/cfml.d.ts +5 -0
  37. package/dist/src/generators/cfml.js +134 -0
  38. package/dist/src/generators/cfml.js.map +1 -0
  39. package/dist/src/generators/clojure.d.ts +7 -0
  40. package/dist/src/generators/clojure.js +436 -0
  41. package/dist/src/generators/clojure.js.map +1 -0
  42. package/dist/src/generators/csharp.d.ts +8 -0
  43. package/dist/src/generators/csharp.js +356 -0
  44. package/dist/src/generators/csharp.js.map +1 -0
  45. package/dist/src/generators/dart.d.ts +5 -0
  46. package/dist/src/generators/dart.js +273 -0
  47. package/dist/src/generators/dart.js.map +1 -0
  48. package/dist/src/generators/elixir.d.ts +8 -0
  49. package/dist/src/generators/elixir.js +296 -0
  50. package/dist/src/generators/elixir.js.map +1 -0
  51. package/dist/src/generators/go.d.ts +5 -0
  52. package/dist/src/generators/go.js +228 -0
  53. package/dist/src/generators/go.js.map +1 -0
  54. package/dist/src/generators/har.d.ts +7 -0
  55. package/dist/src/generators/har.js +115 -0
  56. package/dist/src/generators/har.js.map +1 -0
  57. package/dist/src/generators/http.d.ts +7 -0
  58. package/dist/src/generators/http.js +165 -0
  59. package/dist/src/generators/http.js.map +1 -0
  60. package/dist/src/generators/httpie.d.ts +5 -0
  61. package/dist/src/generators/httpie.js +446 -0
  62. package/dist/src/generators/httpie.js.map +1 -0
  63. package/dist/src/generators/java/httpurlconnection.d.ts +5 -0
  64. package/dist/src/generators/java/httpurlconnection.js +123 -0
  65. package/dist/src/generators/java/httpurlconnection.js.map +1 -0
  66. package/dist/src/generators/java/java.d.ts +8 -0
  67. package/dist/src/generators/java/java.js +247 -0
  68. package/dist/src/generators/java/java.js.map +1 -0
  69. package/dist/src/generators/java/jsoup.d.ts +5 -0
  70. package/dist/src/generators/java/jsoup.js +152 -0
  71. package/dist/src/generators/java/jsoup.js.map +1 -0
  72. package/dist/src/generators/java/okhttp.d.ts +5 -0
  73. package/dist/src/generators/java/okhttp.js +232 -0
  74. package/dist/src/generators/java/okhttp.js.map +1 -0
  75. package/dist/src/generators/javascript/axios.d.ts +5 -0
  76. package/dist/src/generators/javascript/axios.js +332 -0
  77. package/dist/src/generators/javascript/axios.js.map +1 -0
  78. package/dist/src/generators/javascript/got.d.ts +5 -0
  79. package/dist/src/generators/javascript/got.js +277 -0
  80. package/dist/src/generators/javascript/got.js.map +1 -0
  81. package/dist/src/generators/javascript/http.d.ts +8 -0
  82. package/dist/src/generators/javascript/http.js +183 -0
  83. package/dist/src/generators/javascript/http.js.map +1 -0
  84. package/dist/src/generators/javascript/javascript.d.ts +37 -0
  85. package/dist/src/generators/javascript/javascript.js +676 -0
  86. package/dist/src/generators/javascript/javascript.js.map +1 -0
  87. package/dist/src/generators/javascript/jquery.d.ts +15 -0
  88. package/dist/src/generators/javascript/jquery.js +297 -0
  89. package/dist/src/generators/javascript/jquery.js.map +1 -0
  90. package/dist/src/generators/javascript/ky.d.ts +7 -0
  91. package/dist/src/generators/javascript/ky.js +245 -0
  92. package/dist/src/generators/javascript/ky.js.map +1 -0
  93. package/dist/src/generators/javascript/request.d.ts +5 -0
  94. package/dist/src/generators/javascript/request.js +113 -0
  95. package/dist/src/generators/javascript/request.js.map +1 -0
  96. package/dist/src/generators/javascript/superagent.d.ts +10 -0
  97. package/dist/src/generators/javascript/superagent.js +301 -0
  98. package/dist/src/generators/javascript/superagent.js.map +1 -0
  99. package/dist/src/generators/javascript/xhr.d.ts +8 -0
  100. package/dist/src/generators/javascript/xhr.js +147 -0
  101. package/dist/src/generators/javascript/xhr.js.map +1 -0
  102. package/dist/src/generators/json.d.ts +45 -0
  103. package/dist/src/generators/json.js +197 -0
  104. package/dist/src/generators/json.js.map +1 -0
  105. package/dist/src/generators/julia.d.ts +8 -0
  106. package/dist/src/generators/julia.js +365 -0
  107. package/dist/src/generators/julia.js.map +1 -0
  108. package/dist/src/generators/kotlin.d.ts +8 -0
  109. package/dist/src/generators/kotlin.js +325 -0
  110. package/dist/src/generators/kotlin.js.map +1 -0
  111. package/dist/src/generators/lua.d.ts +5 -0
  112. package/dist/src/generators/lua.js +192 -0
  113. package/dist/src/generators/lua.js.map +1 -0
  114. package/dist/src/generators/matlab/common.d.ts +16 -0
  115. package/dist/src/generators/matlab/common.js +238 -0
  116. package/dist/src/generators/matlab/common.js.map +1 -0
  117. package/dist/src/generators/matlab/httpinterface.d.ts +2 -0
  118. package/dist/src/generators/matlab/httpinterface.js +211 -0
  119. package/dist/src/generators/matlab/httpinterface.js.map +1 -0
  120. package/dist/src/generators/matlab/matlab.d.ts +5 -0
  121. package/dist/src/generators/matlab/matlab.js +29 -0
  122. package/dist/src/generators/matlab/matlab.js.map +1 -0
  123. package/dist/src/generators/matlab/webservices.d.ts +2 -0
  124. package/dist/src/generators/matlab/webservices.js +191 -0
  125. package/dist/src/generators/matlab/webservices.js.map +1 -0
  126. package/dist/src/generators/objectivec.d.ts +8 -0
  127. package/dist/src/generators/objectivec.js +269 -0
  128. package/dist/src/generators/objectivec.js.map +1 -0
  129. package/dist/src/generators/ocaml.d.ts +8 -0
  130. package/dist/src/generators/ocaml.js +150 -0
  131. package/dist/src/generators/ocaml.js.map +1 -0
  132. package/dist/src/generators/perl.d.ts +9 -0
  133. package/dist/src/generators/perl.js +209 -0
  134. package/dist/src/generators/perl.js.map +1 -0
  135. package/dist/src/generators/php/guzzle.d.ts +5 -0
  136. package/dist/src/generators/php/guzzle.js +328 -0
  137. package/dist/src/generators/php/guzzle.js.map +1 -0
  138. package/dist/src/generators/php/php.d.ts +8 -0
  139. package/dist/src/generators/php/php.js +217 -0
  140. package/dist/src/generators/php/php.js.map +1 -0
  141. package/dist/src/generators/php/requests.d.ts +5 -0
  142. package/dist/src/generators/php/requests.js +89 -0
  143. package/dist/src/generators/php/requests.js.map +1 -0
  144. package/dist/src/generators/powershell.d.ts +11 -0
  145. package/dist/src/generators/powershell.js +377 -0
  146. package/dist/src/generators/powershell.js.map +1 -0
  147. package/dist/src/generators/python/http.d.ts +5 -0
  148. package/dist/src/generators/python/http.js +128 -0
  149. package/dist/src/generators/python/http.js.map +1 -0
  150. package/dist/src/generators/python/python.d.ts +22 -0
  151. package/dist/src/generators/python/python.js +1693 -0
  152. package/dist/src/generators/python/python.js.map +1 -0
  153. package/dist/src/generators/r/httr.d.ts +10 -0
  154. package/dist/src/generators/r/httr.js +298 -0
  155. package/dist/src/generators/r/httr.js.map +1 -0
  156. package/dist/src/generators/r/httr2.d.ts +5 -0
  157. package/dist/src/generators/r/httr2.js +214 -0
  158. package/dist/src/generators/r/httr2.js.map +1 -0
  159. package/dist/src/generators/ruby/httparty.d.ts +6 -0
  160. package/dist/src/generators/ruby/httparty.js +308 -0
  161. package/dist/src/generators/ruby/httparty.js.map +1 -0
  162. package/dist/src/generators/ruby/ruby.d.ts +14 -0
  163. package/dist/src/generators/ruby/ruby.js +515 -0
  164. package/dist/src/generators/ruby/ruby.js.map +1 -0
  165. package/dist/src/generators/rust.d.ts +8 -0
  166. package/dist/src/generators/rust.js +177 -0
  167. package/dist/src/generators/rust.js.map +1 -0
  168. package/dist/src/generators/swift.d.ts +8 -0
  169. package/dist/src/generators/swift.js +219 -0
  170. package/dist/src/generators/swift.js.map +1 -0
  171. package/dist/src/generators/wget.d.ts +8 -0
  172. package/dist/src/generators/wget.js +472 -0
  173. package/dist/src/generators/wget.js.map +1 -0
  174. package/dist/src/index.d.ts +53 -0
  175. package/dist/src/index.js +54 -0
  176. package/dist/src/index.js.map +1 -0
  177. package/dist/src/parse.d.ts +13 -0
  178. package/dist/src/parse.js +43 -0
  179. package/dist/src/parse.js.map +1 -0
  180. package/dist/src/shell/Parser.d.ts +4 -0
  181. package/dist/src/shell/Parser.js +6 -0
  182. package/dist/src/shell/Parser.js.map +1 -0
  183. package/dist/src/shell/Word.d.ts +48 -0
  184. package/dist/src/shell/Word.js +486 -0
  185. package/dist/src/shell/Word.js.map +1 -0
  186. package/dist/src/shell/tokenizer.d.ts +3 -0
  187. package/dist/src/shell/tokenizer.js +610 -0
  188. package/dist/src/shell/tokenizer.js.map +1 -0
  189. package/dist/src/shell/webParser.d.ts +4 -0
  190. package/dist/src/shell/webParser.js +15 -0
  191. package/dist/src/shell/webParser.js.map +1 -0
  192. package/dist/src/utils.d.ts +6 -0
  193. package/dist/src/utils.js +21 -0
  194. package/dist/src/utils.js.map +1 -0
  195. package/dist/tools/compare-requests.d.ts +2 -0
  196. package/dist/tools/compare-requests.js +448 -0
  197. package/dist/tools/compare-requests.js.map +1 -0
  198. package/dist/tools/gen-test.d.ts +2 -0
  199. package/dist/tools/gen-test.js +114 -0
  200. package/dist/tools/gen-test.js.map +1 -0
  201. package/dist/tree-sitter-bash.wasm +0 -0
  202. package/index.d.ts +3 -0
  203. package/package.json +97 -0
  204. package/src/Headers.ts +334 -0
  205. package/src/Query.ts +147 -0
  206. package/src/Request.ts +1649 -0
  207. package/src/Warnings.ts +154 -0
  208. package/src/cli.ts +561 -0
  209. package/src/curl/auth.ts +45 -0
  210. package/src/curl/form.ts +297 -0
  211. package/src/curl/opts.ts +1417 -0
  212. package/src/curl/url.ts +241 -0
  213. package/src/generators/ansible.ts +364 -0
  214. package/src/generators/c.ts +1371 -0
  215. package/src/generators/cfml.ts +155 -0
  216. package/src/generators/clojure.ts +521 -0
  217. package/src/generators/csharp.ts +383 -0
  218. package/src/generators/dart.ts +293 -0
  219. package/src/generators/elixir.ts +349 -0
  220. package/src/generators/go.ts +269 -0
  221. package/src/generators/har.ts +147 -0
  222. package/src/generators/http.ts +194 -0
  223. package/src/generators/httpie.ts +506 -0
  224. package/src/generators/java/httpurlconnection.ts +151 -0
  225. package/src/generators/java/java.ts +280 -0
  226. package/src/generators/java/jsoup.ts +172 -0
  227. package/src/generators/java/okhttp.ts +257 -0
  228. package/src/generators/javascript/axios.ts +395 -0
  229. package/src/generators/javascript/got.ts +362 -0
  230. package/src/generators/javascript/http.ts +232 -0
  231. package/src/generators/javascript/javascript.ts +825 -0
  232. package/src/generators/javascript/jquery.ts +360 -0
  233. package/src/generators/javascript/ky.ts +315 -0
  234. package/src/generators/javascript/request.ts +152 -0
  235. package/src/generators/javascript/superagent.ts +373 -0
  236. package/src/generators/javascript/xhr.ts +192 -0
  237. package/src/generators/json.ts +293 -0
  238. package/src/generators/julia.ts +398 -0
  239. package/src/generators/kotlin.ts +355 -0
  240. package/src/generators/lua.ts +209 -0
  241. package/src/generators/matlab/common.ts +281 -0
  242. package/src/generators/matlab/httpinterface.ts +284 -0
  243. package/src/generators/matlab/matlab.ts +39 -0
  244. package/src/generators/matlab/webservices.ts +263 -0
  245. package/src/generators/objectivec.ts +297 -0
  246. package/src/generators/ocaml.ts +168 -0
  247. package/src/generators/perl.ts +237 -0
  248. package/src/generators/php/guzzle.ts +368 -0
  249. package/src/generators/php/php.ts +239 -0
  250. package/src/generators/php/requests.ts +103 -0
  251. package/src/generators/powershell.ts +430 -0
  252. package/src/generators/python/http.ts +167 -0
  253. package/src/generators/python/python.ts +1934 -0
  254. package/src/generators/r/httr.ts +328 -0
  255. package/src/generators/r/httr2.ts +268 -0
  256. package/src/generators/ruby/httparty.ts +351 -0
  257. package/src/generators/ruby/ruby.ts +564 -0
  258. package/src/generators/rust.ts +243 -0
  259. package/src/generators/swift.ts +235 -0
  260. package/src/generators/wget.ts +533 -0
  261. package/src/index.ts +89 -0
  262. package/src/parse.ts +72 -0
  263. package/src/shell/Parser.ts +8 -0
  264. package/src/shell/Word.ts +531 -0
  265. package/src/shell/tokenizer.ts +736 -0
  266. package/src/shell/webParser.ts +18 -0
  267. package/src/utils.ts +25 -0
  268. package/tools/compare-requests.ts +571 -0
  269. package/tools/extract_curl_args.py +511 -0
  270. package/tools/gen-test.ts +129 -0
@@ -0,0 +1,736 @@
1
+ import { Word, Token, firstShellToken } from "./Word.js";
2
+
3
+ import { CCError } from "../utils.js";
4
+ import { clip } from "../parse.js";
5
+
6
+ import parser from "./Parser.js";
7
+ import type { Parser } from "./Parser.js";
8
+
9
+ import { underlineNode, underlineCursor, type Warnings } from "../Warnings.js";
10
+
11
+ const BACKSLASHES = /\\./gs;
12
+ function removeBackslash(m: string) {
13
+ return m.charAt(1) === "\n" ? "" : m.charAt(1);
14
+ }
15
+ function removeBackslashes(str: string): string {
16
+ return str.replace(BACKSLASHES, removeBackslash);
17
+ }
18
+ // https://www.gnu.org/software/bash/manual/bash.html#Double-Quotes
19
+ const DOUBLE_QUOTE_BACKSLASHES = /\\[\\$`"\n]/gs;
20
+ function removeDoubleQuoteBackslashes(str: string): string {
21
+ return str.replace(DOUBLE_QUOTE_BACKSLASHES, removeBackslash);
22
+ }
23
+ // ANSI-C quoted strings look $'like this'.
24
+ // Not all shells have them but Bash does
25
+ // https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
26
+ //
27
+ // https://git.savannah.gnu.org/cgit/bash.git/tree/lib/sh/strtrans.c
28
+ const ANSI_BACKSLASHES =
29
+ /\\(\\|a|b|e|E|f|n|r|t|v|'|"|\?|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{1,4}|U[0-9A-Fa-f]{1,8}|c.)/gs;
30
+ function removeAnsiCBackslashes(str: string): string {
31
+ function unescapeChar(m: string) {
32
+ switch (m.charAt(1)) {
33
+ case "\\":
34
+ return "\\";
35
+ case "a":
36
+ return "\x07";
37
+ case "b":
38
+ return "\b";
39
+ case "e":
40
+ case "E":
41
+ return "\x1B";
42
+ case "f":
43
+ return "\f";
44
+ case "n":
45
+ return "\n";
46
+ case "r":
47
+ return "\r";
48
+ case "t":
49
+ return "\t";
50
+ case "v":
51
+ return "\v";
52
+ case "'":
53
+ return "'";
54
+ case '"':
55
+ return '"';
56
+ case "?":
57
+ return "?";
58
+ case "c":
59
+ // Bash handles all characters by considering the first byte
60
+ // of its UTF-8 input and can produce invalid UTF-8, whereas
61
+ // JavaScript stores strings in UTF-16
62
+ if (m.codePointAt(2)! > 127) {
63
+ throw new CCError(
64
+ 'non-ASCII control character in ANSI-C quoted string: "\\u{' +
65
+ m.codePointAt(2)!.toString(16) +
66
+ '}"',
67
+ );
68
+ }
69
+ // If this produces a 0x00 (null) character, it will cause bash to
70
+ // terminate the string at that character, but we return the null
71
+ // character in the result.
72
+ return m[2] === "?"
73
+ ? "\x7F"
74
+ : String.fromCodePoint(
75
+ m[2].toUpperCase().codePointAt(0)! & 0b00011111,
76
+ );
77
+ case "x":
78
+ case "u":
79
+ case "U":
80
+ // Hexadecimal character literal
81
+ // Unlike bash, this will error if the the code point is greater than 10FFFF
82
+ return String.fromCodePoint(parseInt(m.slice(2), 16));
83
+ case "0":
84
+ case "1":
85
+ case "2":
86
+ case "3":
87
+ case "4":
88
+ case "5":
89
+ case "6":
90
+ case "7":
91
+ // Octal character literal
92
+ return String.fromCodePoint(parseInt(m.slice(1), 8) % 256);
93
+ default:
94
+ // There must be a mis-match between ANSI_BACKSLASHES and the switch statement
95
+ throw new CCError(
96
+ "unhandled character in ANSI-C escape code: " + JSON.stringify(m),
97
+ );
98
+ }
99
+ }
100
+
101
+ return str.replace(ANSI_BACKSLASHES, unescapeChar);
102
+ }
103
+
104
+ function toTokens(
105
+ node: Parser.SyntaxNode,
106
+ curlCommand: string,
107
+ warnings: Warnings,
108
+ ): Token[] {
109
+ let vals: Token[] = [];
110
+ switch (node.type) {
111
+ case "$":
112
+ // TODO: https://github.com/tree-sitter/tree-sitter-bash/issues/258
113
+ return ["$"];
114
+ case "word":
115
+ case "number":
116
+ // TODO: number might have a ${variable}
117
+ return [removeBackslashes(node.text)];
118
+ case "raw_string":
119
+ return [node.text.slice(1, -1)];
120
+ case "ansi_c_string":
121
+ return [removeAnsiCBackslashes(node.text.slice(2, -1))];
122
+ case "string":
123
+ case "translated_string": {
124
+ // TODO: MISSING quotes, for example
125
+ // curl "example.com
126
+ let res = "";
127
+ for (const child of node.namedChildren) {
128
+ if (child.type === "string_content") {
129
+ res += removeDoubleQuoteBackslashes(child.text);
130
+ } else {
131
+ // expansion, simple_expansion or command_substitution (or concat?)
132
+ const subVal = toTokens(child, curlCommand, warnings);
133
+ if (typeof subVal === "string") {
134
+ res += subVal;
135
+ } else {
136
+ if (res) {
137
+ vals.push(res);
138
+ res = "";
139
+ }
140
+ vals = vals.concat(subVal);
141
+ }
142
+ }
143
+ }
144
+ if (res || vals.length === 0) {
145
+ vals.push(res);
146
+ }
147
+ return vals;
148
+ }
149
+ case "simple_expansion":
150
+ // TODO: handle variables downstream
151
+ // '$' + variable_name or special_variable_name
152
+ warnings.push([
153
+ "expansion",
154
+ "found environment variable\n" + underlineNode(node, curlCommand),
155
+ ]);
156
+ if (
157
+ node.firstNamedChild &&
158
+ node.firstNamedChild.type === "special_variable_name"
159
+ ) {
160
+ // https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters
161
+ // TODO: warning isn't printed
162
+ warnings.push([
163
+ "special_variable_name",
164
+ node.text +
165
+ " is a special Bash variable\n" +
166
+ underlineNode(node.firstNamedChild, curlCommand),
167
+ ]);
168
+ }
169
+ return [
170
+ {
171
+ type: "variable",
172
+ value: node.text.slice(1),
173
+ text: node.text,
174
+ syntaxNode: node,
175
+ },
176
+ ];
177
+ case "expansion":
178
+ // Expansions look ${like_this}
179
+ // https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
180
+ // TODO: MISSING }, for example
181
+ // curl example${com
182
+ warnings.push([
183
+ "expansion",
184
+ "found expansion expression\n" + underlineNode(node, curlCommand),
185
+ ]);
186
+ // variable_name or subscript or no child
187
+ // TODO: handle substitutions
188
+ return [
189
+ {
190
+ type: "variable",
191
+ value: node.text.slice(2, -1),
192
+ text: node.text,
193
+ syntaxNode: node,
194
+ },
195
+ ];
196
+ case "command_substitution":
197
+ // TODO: MISSING ), for example
198
+ // curl example$(com
199
+ warnings.push([
200
+ "expansion",
201
+ "found command substitution expression\n" +
202
+ underlineNode(node, curlCommand),
203
+ ]);
204
+ return [
205
+ {
206
+ type: "command",
207
+ // TODO: further tokenize and pass an array of args
208
+ // to subprocess.run() or a command name + string args to C#
209
+ value: node.text.slice(node.text.startsWith("$(") ? 2 : 1, -1),
210
+ text: node.text,
211
+ syntaxNode: node,
212
+ },
213
+ ];
214
+ case "concatenation": {
215
+ // item[]=1 turns into item=1 if we don't do this
216
+ // https://github.com/tree-sitter/tree-sitter-bash/issues/104
217
+ let prevEnd = 0;
218
+ let res = "";
219
+ for (const child of node.children) {
220
+ // TODO: removeBackslashes()?
221
+ // Can we get anything other than []{} characters here?
222
+ res += node.text.slice(prevEnd, child.startIndex - node.startIndex);
223
+ prevEnd = child.endIndex - node.startIndex;
224
+
225
+ const subVal = toTokens(child, curlCommand, warnings);
226
+ if (typeof subVal === "string") {
227
+ res += subVal;
228
+ } else {
229
+ if (res) {
230
+ vals.push(res);
231
+ res = "";
232
+ }
233
+ vals = vals.concat(subVal);
234
+ }
235
+ }
236
+ res += node.text.slice(prevEnd);
237
+ if (res || vals.length === 0) {
238
+ vals.push(res);
239
+ }
240
+ return vals;
241
+ }
242
+ default:
243
+ throw new CCError(
244
+ "unexpected syntax node type " +
245
+ JSON.stringify(node.type) +
246
+ '. Must be one of "word", "number", "string", "raw_string", "ansi_c_string", "expansion", "simple_expansion", "translated_string", "command_substitution" or "concatenation"\n' +
247
+ underlineNode(node, curlCommand),
248
+ );
249
+ }
250
+ }
251
+
252
+ function toWord(
253
+ node: Parser.SyntaxNode,
254
+ curlCommand: string,
255
+ warnings: Warnings,
256
+ ): Word {
257
+ return new Word(toTokens(node, curlCommand, warnings));
258
+ }
259
+
260
+ function* traverseLookingForBadNodes(
261
+ tree: Parser.Tree,
262
+ ): Generator<Parser.TreeCursor> {
263
+ const cursor = tree.walk();
264
+
265
+ let reachedRoot = false;
266
+ while (!reachedRoot) {
267
+ if (cursor.nodeType === "ERROR" || cursor.nodeIsMissing) {
268
+ yield cursor;
269
+ }
270
+
271
+ if (cursor.gotoFirstChild()) {
272
+ continue;
273
+ }
274
+
275
+ if (cursor.gotoNextSibling()) {
276
+ continue;
277
+ }
278
+
279
+ let retracing = true;
280
+ while (retracing) {
281
+ if (!cursor.gotoParent()) {
282
+ retracing = false;
283
+ reachedRoot = true;
284
+ }
285
+
286
+ if (cursor.gotoNextSibling()) {
287
+ retracing = false;
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ function warnAboutBadNodes(
294
+ ast: Parser.Tree,
295
+ curlCommand: string,
296
+ warnings: Warnings,
297
+ ) {
298
+ const maxShown = 3;
299
+ let count = 0;
300
+ for (const badNode of traverseLookingForBadNodes(ast)) {
301
+ if (count < maxShown) {
302
+ let underlinedNode = "";
303
+ try {
304
+ underlinedNode = ":\n" + underlineCursor(badNode, curlCommand);
305
+ } catch {}
306
+ const line = badNode.startPosition.row;
307
+ const column = badNode.startPosition.column;
308
+ warnings.push([
309
+ "bash",
310
+ `Bash parsing error on line ${line + 1}` +
311
+ (column !== 0 ? `, column ${column + 1}` : "") +
312
+ underlinedNode,
313
+ ]);
314
+ }
315
+ count += 1;
316
+ }
317
+ const extra = count - maxShown;
318
+ if (extra > 0) {
319
+ warnings.push([
320
+ "bash",
321
+ `${extra} more Bash parsing error${extra > 1 ? "s" : ""} omitted`,
322
+ ]);
323
+ }
324
+ }
325
+
326
+ function warnAboutUselessBackslash(
327
+ n: Parser.SyntaxNode,
328
+ curlCommandLines: string[],
329
+ warnings: Warnings,
330
+ ) {
331
+ const lastCommandLine = curlCommandLines[n.endPosition.row];
332
+ const impromperBackslash = lastCommandLine.match(/\\\s+$/);
333
+ if (
334
+ impromperBackslash &&
335
+ curlCommandLines.length > n.endPosition.row + 1 &&
336
+ impromperBackslash.index !== undefined
337
+ ) {
338
+ warnings.push([
339
+ "unescaped-newline",
340
+ "The trailling '\\' on line " +
341
+ (n.endPosition.row + 1) +
342
+ " is followed by whitespace, so it won't escape the newline after it:\n" +
343
+ // TODO: cut off line if it's very long?
344
+ lastCommandLine +
345
+ "\n" +
346
+ " ".repeat(impromperBackslash.index) +
347
+ "^".repeat(impromperBackslash[0].length),
348
+ ]);
349
+ }
350
+ }
351
+
352
+ function extractRedirect(
353
+ node: Parser.SyntaxNode,
354
+ curlCommand: string,
355
+ warnings: Warnings,
356
+ ): [Parser.SyntaxNode, Word?, Word?] {
357
+ if (!node.namedChildCount) {
358
+ throw new CCError('got empty "redirected_statement" AST node');
359
+ }
360
+
361
+ let stdin, stdinFile;
362
+ const [command, ...redirects] = node.namedChildren;
363
+ if (command.type !== "command") {
364
+ throw new CCError(
365
+ 'got "redirected_statement" AST node whose first child is not a "command", got ' +
366
+ command.type +
367
+ " instead\n" +
368
+ underlineNode(command, curlCommand),
369
+ );
370
+ }
371
+ if (node.childCount < 2) {
372
+ throw new CCError(
373
+ 'got "redirected_statement" AST node with only one child - no redirect',
374
+ );
375
+ }
376
+ if (redirects.length > 1) {
377
+ warnings.push([
378
+ "multiple-redirects",
379
+ // TODO: this is misleading because not all generators support redirects
380
+ "found " +
381
+ redirects.length +
382
+ " redirect nodes. Only the first one will be used:\n" +
383
+ underlineNode(redirects[1], curlCommand),
384
+ ]);
385
+ }
386
+ const redirect = redirects[0];
387
+ if (redirect.type === "file_redirect") {
388
+ const destination = redirect.childForFieldName("destination");
389
+ if (!destination) {
390
+ throw new CCError(
391
+ 'got "file_redirect" AST node with no "destination" child',
392
+ );
393
+ }
394
+ stdinFile = toWord(destination, curlCommand, warnings);
395
+ } else if (redirect.type === "heredoc_redirect") {
396
+ const heredocBody = redirect.descendantsOfType("heredoc_body")[0];
397
+ if (!heredocBody) {
398
+ throw new CCError(
399
+ 'got "redirected_statement" AST node without heredoc_body',
400
+ );
401
+ }
402
+ // TODO: heredocs can do variable expansion and stuff
403
+ stdin = new Word(heredocBody.text);
404
+ } else if (redirect.type === "herestring_redirect") {
405
+ // TODO: herestring_redirect can be in a command node
406
+ // https://github.com/tree-sitter/tree-sitter-bash/issues/260
407
+ if (redirect.namedChildCount < 1 || !redirect.firstNamedChild) {
408
+ throw new CCError(
409
+ 'got "redirected_statement" AST node with empty herestring',
410
+ );
411
+ }
412
+ stdin = toWord(redirect.firstNamedChild, curlCommand, warnings);
413
+ } else {
414
+ throw new CCError(
415
+ 'got "redirected_statement" AST node whose second child is not one of "file_redirect", "heredoc_redirect" or "herestring_redirect", got ' +
416
+ command.type +
417
+ " instead",
418
+ );
419
+ }
420
+ return [command, stdin, stdinFile];
421
+ }
422
+
423
+ function _findCurlInPipeline(
424
+ node: Parser.SyntaxNode,
425
+ curlCommand: string,
426
+ warnings: Warnings,
427
+ ): [Parser.SyntaxNode?, Word?, Word?] {
428
+ let command, stdin, stdinFile;
429
+ for (const child of node.namedChildren) {
430
+ if (child.type === "command") {
431
+ const commandName = child.namedChildren[0];
432
+ if (commandName.type !== "command_name") {
433
+ throw new CCError(
434
+ 'got "command" AST node whose first child is not a "command_name", got ' +
435
+ commandName.type +
436
+ " instead\n" +
437
+ underlineNode(commandName, curlCommand),
438
+ );
439
+ }
440
+ const commandNameWord = commandName.namedChildren[0];
441
+ if (commandNameWord.type !== "word") {
442
+ throw new CCError(
443
+ 'got "command_name" AST node whose first child is not a "word", got ' +
444
+ commandNameWord.type +
445
+ " instead\n" +
446
+ underlineNode(commandNameWord, curlCommand),
447
+ );
448
+ }
449
+ if (commandNameWord.text === "curl") {
450
+ if (!command) {
451
+ command = child;
452
+ } else {
453
+ warnings.push([
454
+ "multiple-curl-in-pipeline",
455
+ "found multiple curl commands in pipeline:\n" +
456
+ underlineNode(child, curlCommand),
457
+ ]);
458
+ }
459
+ }
460
+ } else if (child.type === "redirected_statement") {
461
+ const [redirCommand, redirStdin, redirStdinFile] = extractRedirect(
462
+ child,
463
+ curlCommand,
464
+ warnings,
465
+ );
466
+ if (redirCommand.namedChildren[0].text === "curl") {
467
+ if (!command) {
468
+ [command, stdin, stdinFile] = [
469
+ redirCommand,
470
+ redirStdin,
471
+ redirStdinFile,
472
+ ];
473
+ } else {
474
+ warnings.push([
475
+ "multiple-curl-in-pipeline",
476
+ "found multiple curl commands in pipeline:\n" +
477
+ underlineNode(redirCommand, curlCommand),
478
+ ]);
479
+ }
480
+ }
481
+ } else if (child.type === "pipeline") {
482
+ // pipelines can be nested
483
+ // https://github.com/tree-sitter/tree-sitter-bash/issues/167
484
+ const [nestedCommand, nestedStdin, nestedStdinFile] = _findCurlInPipeline(
485
+ child,
486
+ curlCommand,
487
+ warnings,
488
+ );
489
+ if (!nestedCommand) {
490
+ continue;
491
+ }
492
+ if (nestedCommand.namedChildren[0].text === "curl") {
493
+ if (!command) {
494
+ [command, stdin, stdinFile] = [
495
+ nestedCommand,
496
+ nestedStdin,
497
+ nestedStdinFile,
498
+ ];
499
+ } else {
500
+ warnings.push([
501
+ "multiple-curl-in-pipeline",
502
+ "found multiple curl commands in pipeline:\n" +
503
+ underlineNode(nestedCommand, curlCommand),
504
+ ]);
505
+ }
506
+ }
507
+ }
508
+ }
509
+ return [command, stdin, stdinFile];
510
+ }
511
+
512
+ // TODO: use pipeline input/output redirects,
513
+ // i.e. add stdinCommand and stdout/stdoutFile/stdoutCommand
514
+ function findCurlInPipeline(
515
+ node: Parser.SyntaxNode,
516
+ curlCommand: string,
517
+ warnings: Warnings,
518
+ ): [Parser.SyntaxNode, Word?, Word?] {
519
+ const [command, stdin, stdinFile] = _findCurlInPipeline(
520
+ node,
521
+ curlCommand,
522
+ warnings,
523
+ );
524
+ if (!command) {
525
+ throw new CCError(
526
+ "could not find curl command in pipeline\n" +
527
+ underlineNode(node, curlCommand),
528
+ );
529
+ }
530
+ return [command, stdin, stdinFile];
531
+ }
532
+
533
+ // TODO: check entire AST for ERROR/MISSING nodes
534
+ // TODO: get all command nodes
535
+ function extractCommandNodes(
536
+ ast: Parser.Tree,
537
+ curlCommand: string,
538
+ warnings: Warnings,
539
+ ): [Parser.SyntaxNode, Word?, Word?][] {
540
+ // https://github.com/tree-sitter/tree-sitter-bash/blob/master/grammar.js
541
+ // The AST must be in a nice format, i.e.
542
+ // (program
543
+ // (command
544
+ // name: (command_name (word))
545
+ // argument+: (
546
+ // word |
547
+ // "string" |
548
+ // 'raw_string' |
549
+ // $'ansi_c_string' |
550
+ // $"translated_string" |
551
+ // ${expansion} |
552
+ // $simple_expansion |
553
+ // concatenation)))
554
+ // or
555
+ // (program
556
+ // (redirected_statement
557
+ // body: (command, same as above)
558
+ // redirect))
559
+
560
+ // Shouldn't happen.
561
+ if (ast.rootNode.type !== "program") {
562
+ // TODO: better error message.
563
+ throw new CCError(
564
+ // TODO: expand "AST" acronym the first time it appears in an error message
565
+ 'expected a "program" top-level AST node, got ' +
566
+ ast.rootNode.type +
567
+ " instead",
568
+ );
569
+ }
570
+
571
+ if (ast.rootNode.namedChildCount < 1 || !ast.rootNode.namedChildren) {
572
+ // TODO: better error message.
573
+ throw new CCError('empty "program" node');
574
+ }
575
+
576
+ const curlCommandLines = curlCommand.split("\n");
577
+ let sawComment = false;
578
+ const commands: [Parser.SyntaxNode, Word?, Word?][] = [];
579
+ // Get top-level command and redirected_statement AST nodes, skipping comments
580
+ for (const n of ast.rootNode.namedChildren) {
581
+ switch (n.type) {
582
+ case "comment":
583
+ sawComment = true;
584
+ continue;
585
+ case "command":
586
+ commands.push([n, undefined, undefined]);
587
+ warnAboutUselessBackslash(n, curlCommandLines, warnings);
588
+ break;
589
+ case "redirected_statement":
590
+ commands.push(extractRedirect(n, curlCommand, warnings));
591
+ warnAboutUselessBackslash(n, curlCommandLines, warnings);
592
+ break;
593
+ case "pipeline":
594
+ commands.push(findCurlInPipeline(n, curlCommand, warnings));
595
+ warnAboutUselessBackslash(n, curlCommandLines, warnings);
596
+ break;
597
+ case "heredoc_body": // https://github.com/tree-sitter/tree-sitter-bash/issues/118
598
+ continue;
599
+ case "ERROR":
600
+ throw new CCError(
601
+ `Bash parsing error on line ${n.startPosition.row + 1}:\n` +
602
+ underlineNode(n, curlCommand),
603
+ );
604
+ default:
605
+ // TODO: better error message.
606
+ throw new CCError(
607
+ "found " +
608
+ JSON.stringify(n.type) +
609
+ ' AST node, only "command", "pipeline" or "redirected_statement" are supported\n' +
610
+ underlineNode(n, curlCommand),
611
+ );
612
+ }
613
+ }
614
+ if (!commands.length) {
615
+ // NOTE: if you add more node types in the `for` loop above, this error needs to be updated.
616
+ // We would probably need to keep track of the node types we've seen.
617
+ throw new CCError(
618
+ 'expected a "command" or "redirected_statement" AST node' +
619
+ (sawComment ? ', only found "comment" nodes' : ""),
620
+ );
621
+ }
622
+
623
+ return commands;
624
+ }
625
+
626
+ function toNameAndArgv(
627
+ command: Parser.SyntaxNode,
628
+ curlCommand: string,
629
+ warnings: Warnings,
630
+ ): [Parser.SyntaxNode, Parser.SyntaxNode[]] {
631
+ if (command.childCount < 1) {
632
+ // TODO: better error message.
633
+ throw new CCError(
634
+ 'empty "command" node\n' + underlineNode(command, curlCommand),
635
+ );
636
+ }
637
+
638
+ // TODO: warn about leading variable_assignment s
639
+ const name = command.childForFieldName("name");
640
+ const args = command.childrenForFieldName("argument");
641
+ // TODO: find if one changes stdin
642
+ // const redirects = command.childrenForFieldName("redirect");
643
+
644
+ if (!name) {
645
+ throw new CCError(
646
+ 'found "command" AST node with no "command_name" child\n' +
647
+ underlineNode(command, curlCommand),
648
+ );
649
+ }
650
+
651
+ return [name, args];
652
+ }
653
+
654
+ // Checks that name is "curl"
655
+ function nameToWord(
656
+ name: Parser.SyntaxNode,
657
+ curlCommand: string,
658
+ warnings: Warnings,
659
+ ): Word {
660
+ if (name.childCount < 1 || !name.firstChild) {
661
+ throw new CCError(
662
+ 'found empty "command_name" AST node\n' +
663
+ underlineNode(name, curlCommand),
664
+ );
665
+ } else if (name.childCount > 1) {
666
+ warnings.push([
667
+ "extra-command_name-children",
668
+ 'expected "command_name" node to only have one child but it has ' +
669
+ name.childCount,
670
+ ]);
671
+ }
672
+
673
+ const nameNode = name.firstChild;
674
+ const nameWord = toWord(nameNode, curlCommand, warnings);
675
+ const nameWordStr = nameWord.toString();
676
+ const cmdNameShellToken = firstShellToken(nameWord);
677
+ if (cmdNameShellToken) {
678
+ // The most common reason for the command name to contain an expression
679
+ // is probably users accidentally copying a $ from the shell prompt
680
+ // without a space after it
681
+ if (nameWordStr !== "$curl") {
682
+ // TODO: or just assume it evaluates to "curl"?
683
+ throw new CCError(
684
+ "expected command name to be a simple value but found a " +
685
+ cmdNameShellToken.type +
686
+ "\n" +
687
+ underlineNode(cmdNameShellToken.syntaxNode, curlCommand),
688
+ );
689
+ }
690
+ } else if (nameWordStr.trim() !== "curl") {
691
+ const c = nameWordStr.trim();
692
+ if (!c) {
693
+ throw new CCError(
694
+ "found command without a command_name\n" +
695
+ underlineNode(nameNode, curlCommand),
696
+ );
697
+ }
698
+ throw new CCError(
699
+ 'command should begin with "curl" but instead begins with ' +
700
+ JSON.stringify(clip(c)) +
701
+ "\n" +
702
+ underlineNode(nameNode, curlCommand),
703
+ );
704
+ }
705
+ return nameWord;
706
+ }
707
+
708
+ export function tokenize(
709
+ curlCommand: string,
710
+ warnings: Warnings = [],
711
+ ): [Word[], Word?, Word?][] {
712
+ const ast = parser.parse(curlCommand);
713
+ warnAboutBadNodes(ast, curlCommand, warnings);
714
+
715
+ // TODO: pass syntax nodes for each token downstream and use it to
716
+ // highlight the problematic parts in warnings/errors so that it's clear
717
+ // which command a warning/error is for
718
+ // TODO: could even go further and pass all start/end indexes of all
719
+ // values all the way through the conversion so when you mouse over a part of
720
+ // the generated Python code we could highlight the part of the curl command
721
+ // that it came from.
722
+ const commandNodes = extractCommandNodes(ast, curlCommand, warnings);
723
+ const commands: [Word[], Word?, Word?][] = [];
724
+ for (const [command, stdin, stdinFile] of commandNodes) {
725
+ const [name, argv] = toNameAndArgv(command, curlCommand, warnings);
726
+ commands.push([
727
+ [
728
+ nameToWord(name, curlCommand, warnings),
729
+ ...argv.map((arg) => toWord(arg, curlCommand, warnings)),
730
+ ],
731
+ stdin,
732
+ stdinFile,
733
+ ]);
734
+ }
735
+ return commands;
736
+ }