@uwdata/mosaic-sql 0.11.0 → 0.12.1

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 (143) hide show
  1. package/README.md +2 -0
  2. package/dist/mosaic-sql.js +2242 -1064
  3. package/dist/mosaic-sql.min.js +1 -1
  4. package/dist/types/ast/aggregate.d.ts +70 -0
  5. package/dist/types/ast/between-op.d.ts +46 -0
  6. package/dist/types/ast/binary-op.d.ts +28 -0
  7. package/dist/types/ast/case.d.ts +68 -0
  8. package/dist/types/ast/cast.d.ts +21 -0
  9. package/dist/types/ast/column-param.d.ts +17 -0
  10. package/dist/types/ast/column-ref.d.ts +39 -0
  11. package/dist/types/ast/fragment.d.ts +14 -0
  12. package/dist/types/ast/from.d.ts +21 -0
  13. package/dist/types/ast/function.d.ts +21 -0
  14. package/dist/types/ast/in-op.d.ts +21 -0
  15. package/dist/types/ast/interval.d.ts +21 -0
  16. package/dist/types/ast/literal.d.ts +15 -0
  17. package/dist/types/ast/logical-op.d.ts +46 -0
  18. package/dist/types/ast/node.d.ts +24 -0
  19. package/dist/types/ast/order-by.d.ts +29 -0
  20. package/dist/types/ast/param.d.ts +19 -0
  21. package/dist/types/ast/query.d.ts +268 -0
  22. package/dist/types/ast/sample.d.ts +42 -0
  23. package/dist/types/ast/select.d.ts +22 -0
  24. package/dist/types/ast/table-ref.d.ts +25 -0
  25. package/dist/types/ast/unary-op.d.ts +39 -0
  26. package/dist/types/ast/verbatim.d.ts +9 -0
  27. package/dist/types/ast/window.d.ts +177 -0
  28. package/dist/types/ast/with.d.ts +22 -0
  29. package/dist/types/constants.d.ts +38 -0
  30. package/dist/types/functions/aggregate.d.ts +229 -0
  31. package/dist/types/functions/case.d.ts +15 -0
  32. package/dist/types/functions/cast.d.ts +26 -0
  33. package/dist/types/functions/column.d.ts +9 -0
  34. package/dist/types/functions/datetime.d.ts +44 -0
  35. package/dist/types/functions/literal.d.ts +16 -0
  36. package/dist/types/functions/numeric.d.ts +93 -0
  37. package/dist/types/functions/operators.d.ts +198 -0
  38. package/dist/types/functions/order-by.d.ts +17 -0
  39. package/dist/types/functions/spatial.d.ts +37 -0
  40. package/dist/types/functions/sql-template-tag.d.ts +16 -0
  41. package/dist/types/functions/string.d.ts +55 -0
  42. package/dist/types/functions/table-ref.d.ts +9 -0
  43. package/dist/types/functions/window.d.ts +87 -0
  44. package/dist/types/index-types.d.ts +2 -0
  45. package/dist/types/index.d.ts +53 -0
  46. package/dist/types/load/create.d.ts +8 -0
  47. package/dist/types/load/extension.d.ts +1 -0
  48. package/dist/types/load/load.d.ts +12 -0
  49. package/dist/types/load/sql-from.d.ts +11 -0
  50. package/dist/types/transforms/bin-1d.d.ts +14 -0
  51. package/dist/types/transforms/bin-2d.d.ts +18 -0
  52. package/dist/types/transforms/bin-linear-1d.d.ts +9 -0
  53. package/dist/types/transforms/bin-linear-2d.d.ts +18 -0
  54. package/dist/types/transforms/line-density.d.ts +23 -0
  55. package/dist/types/transforms/m4.d.ts +18 -0
  56. package/dist/types/transforms/scales.d.ts +1 -0
  57. package/dist/types/types.d.ts +59 -0
  58. package/dist/types/util/ast.d.ts +60 -0
  59. package/dist/types/util/function.d.ts +54 -0
  60. package/dist/types/util/string.d.ts +3 -0
  61. package/dist/types/util/type-check.d.ts +18 -0
  62. package/dist/types/visit/recurse.d.ts +28 -0
  63. package/dist/types/visit/rewrite.d.ts +10 -0
  64. package/dist/types/visit/visitors.d.ts +33 -0
  65. package/dist/types/visit/walk.d.ts +7 -0
  66. package/jsconfig.json +11 -0
  67. package/package.json +6 -4
  68. package/src/ast/aggregate.js +164 -0
  69. package/src/ast/between-op.js +75 -0
  70. package/src/ast/binary-op.js +40 -0
  71. package/src/ast/case.js +105 -0
  72. package/src/ast/cast.js +34 -0
  73. package/src/ast/column-param.js +29 -0
  74. package/src/ast/column-ref.js +72 -0
  75. package/src/ast/fragment.js +26 -0
  76. package/src/ast/from.js +40 -0
  77. package/src/ast/function.js +34 -0
  78. package/src/ast/in-op.js +33 -0
  79. package/src/ast/interval.js +33 -0
  80. package/src/ast/literal.js +55 -0
  81. package/src/ast/logical-op.js +67 -0
  82. package/src/ast/node.js +29 -0
  83. package/src/ast/order-by.js +48 -0
  84. package/src/ast/param.js +35 -0
  85. package/src/ast/query.js +578 -0
  86. package/src/ast/sample.js +53 -0
  87. package/src/ast/select.js +44 -0
  88. package/src/ast/table-ref.js +44 -0
  89. package/src/ast/unary-op.js +64 -0
  90. package/src/ast/verbatim.js +26 -0
  91. package/src/ast/window.js +290 -0
  92. package/src/ast/with.js +30 -0
  93. package/src/constants.js +44 -0
  94. package/src/functions/aggregate.js +335 -0
  95. package/src/functions/case.js +21 -0
  96. package/src/functions/cast.js +39 -0
  97. package/src/functions/column.js +20 -0
  98. package/src/functions/datetime.js +65 -0
  99. package/src/functions/literal.js +22 -0
  100. package/src/functions/numeric.js +139 -0
  101. package/src/functions/operators.js +298 -0
  102. package/src/functions/order-by.js +24 -0
  103. package/src/functions/spatial.js +56 -0
  104. package/src/functions/sql-template-tag.js +51 -0
  105. package/src/functions/string.js +82 -0
  106. package/src/functions/table-ref.js +14 -0
  107. package/src/functions/window.js +121 -0
  108. package/src/index-types.ts +2 -0
  109. package/src/index.js +57 -155
  110. package/src/load/create.js +10 -2
  111. package/src/load/load.js +4 -4
  112. package/src/load/sql-from.js +7 -6
  113. package/src/transforms/bin-1d.js +21 -0
  114. package/src/transforms/bin-2d.js +29 -0
  115. package/src/transforms/bin-linear-1d.js +26 -0
  116. package/src/transforms/bin-linear-2d.js +71 -0
  117. package/src/transforms/line-density.js +113 -0
  118. package/src/transforms/m4.js +38 -0
  119. package/src/{scales.js → transforms/scales.js} +31 -17
  120. package/src/types.ts +96 -0
  121. package/src/util/ast.js +96 -0
  122. package/src/util/function.js +78 -0
  123. package/src/util/string.js +16 -0
  124. package/src/util/type-check.js +29 -0
  125. package/src/visit/recurse.js +57 -0
  126. package/src/visit/rewrite.js +32 -0
  127. package/src/visit/visitors.js +108 -0
  128. package/src/visit/walk.js +30 -0
  129. package/tsconfig.json +12 -0
  130. package/src/Query.js +0 -593
  131. package/src/aggregates.js +0 -185
  132. package/src/cast.js +0 -19
  133. package/src/datetime.js +0 -31
  134. package/src/desc.js +0 -13
  135. package/src/expression.js +0 -170
  136. package/src/functions.js +0 -25
  137. package/src/literal.js +0 -6
  138. package/src/operators.js +0 -54
  139. package/src/ref.js +0 -109
  140. package/src/repeat.js +0 -3
  141. package/src/spatial.js +0 -10
  142. package/src/to-sql.js +0 -52
  143. package/src/windows.js +0 -239
package/src/Query.js DELETED
@@ -1,593 +0,0 @@
1
- import { isSQLExpression } from './expression.js';
2
- import { asColumn, asRelation, isColumnRefFor, Ref } from './ref.js';
3
-
4
- export class Query {
5
-
6
- static select(...expr) {
7
- return new Query().select(...expr);
8
- }
9
-
10
- static from(...expr) {
11
- return new Query().from(...expr);
12
- }
13
-
14
- static with(...expr) {
15
- return new Query().with(...expr);
16
- }
17
-
18
- static union(...queries) {
19
- return new SetOperation('UNION', queries.flat());
20
- }
21
-
22
- static unionAll(...queries) {
23
- return new SetOperation('UNION ALL', queries.flat());
24
- }
25
-
26
- static intersect(...queries) {
27
- return new SetOperation('INTERSECT', queries.flat());
28
- }
29
-
30
- static except(...queries) {
31
- return new SetOperation('EXCEPT', queries.flat());
32
- }
33
-
34
- static describe(query) {
35
- const q = query.clone();
36
- const { clone, toString } = q;
37
- return Object.assign(q, {
38
- describe: true,
39
- clone: () => Query.describe(clone.call(q)),
40
- toString: () => `DESCRIBE ${toString.call(q)}`
41
- });
42
- }
43
-
44
- constructor() {
45
- this.query = {
46
- with: [],
47
- select: [],
48
- from: [],
49
- where: [],
50
- groupby: [],
51
- having: [],
52
- window: [],
53
- qualify: [],
54
- orderby: []
55
- };
56
- this.cteFor = null;
57
- }
58
-
59
- clone() {
60
- const q = new Query();
61
- q.query = { ...this.query };
62
- return q;
63
- }
64
-
65
- /**
66
- * Retrieve current WITH common table expressions (CTEs).
67
- * @returns {any[]}
68
- *//**
69
- * Add WITH common table expressions (CTEs).
70
- * @param {...any} expr Expressions to add.
71
- * @returns {this}
72
- */
73
- with(...expr) {
74
- const { query } = this;
75
- if (expr.length === 0) {
76
- // @ts-ignore
77
- return query.with;
78
- } else {
79
- const list = [];
80
- const add = (as, q) => {
81
- const query = q.clone();
82
- query.cteFor = this;
83
- list.push({ as, query });
84
- };
85
- expr.flat().forEach(e => {
86
- if (e == null) {
87
- // do nothing
88
- } else if (e.as && e.query) {
89
- add(e.as, e.query);
90
- } else {
91
- for (const as in e) {
92
- add(as, e[as]);
93
- }
94
- }
95
- });
96
- query.with = query.with.concat(list);
97
- return this;
98
- }
99
- }
100
-
101
- /**
102
- * Retrieve current SELECT expressions.
103
- * @returns {any[]}
104
- *//**
105
- * Add SELECT expressions.
106
- * @param {...any} expr Expressions to add.
107
- * @returns {this}
108
- */
109
- select(...expr) {
110
- const { query } = this;
111
- if (expr.length === 0) {
112
- // @ts-ignore
113
- return query.select;
114
- } else {
115
- const list = [];
116
- for (const e of expr.flat()) {
117
- if (e == null) {
118
- // do nothing
119
- } else if (typeof e === 'string') {
120
- list.push({ as: e, expr: asColumn(e) });
121
- } else if (e instanceof Ref) {
122
- list.push({ as: e.column, expr: e });
123
- } else if (Array.isArray(e)) {
124
- list.push({ as: e[0], expr: e[1] });
125
- } else {
126
- for (const as in e) {
127
- list.push({ as: unquote(as), expr: asColumn(e[as]) });
128
- }
129
- }
130
- }
131
-
132
- const keys = new Set(list.map(x => x.as));
133
- query.select = query.select
134
- .filter(x => !keys.has(x.as))
135
- .concat(list.filter(x => x.expr));
136
- return this;
137
- }
138
- }
139
-
140
- $select(...expr) {
141
- this.query.select = [];
142
- return this.select(...expr);
143
- }
144
-
145
- distinct(value = true) {
146
- this.query.distinct = !!value;
147
- return this;
148
- }
149
-
150
- /**
151
- * Retrieve current from expressions.
152
- * @returns {any[]}
153
- *//**
154
- * Provide table from expressions.
155
- * @param {...any} expr
156
- * @returns {this}
157
- */
158
- from(...expr) {
159
- const { query } = this;
160
- if (expr.length === 0) {
161
- // @ts-ignore
162
- return query.from;
163
- } else {
164
- const list = [];
165
- expr.flat().forEach(e => {
166
- if (e == null) {
167
- // do nothing
168
- } else if (typeof e === 'string') {
169
- list.push({ as: e, from: asRelation(e) });
170
- } else if (e instanceof Ref) {
171
- list.push({ as: e.table, from: e });
172
- } else if (isQuery(e) || isSQLExpression(e)) {
173
- list.push({ from: e });
174
- } else if (Array.isArray(e)) {
175
- list.push({ as: unquote(e[0]), from: asRelation(e[1]) });
176
- } else {
177
- for (const as in e) {
178
- list.push({ as: unquote(as), from: asRelation(e[as]) });
179
- }
180
- }
181
- });
182
- query.from = query.from.concat(list);
183
- return this;
184
- }
185
- }
186
-
187
- $from(...expr) {
188
- this.query.from = [];
189
- return this.from(...expr);
190
- }
191
-
192
- /**
193
- * Retrieve current SAMPLE settings.
194
- * @returns {any[]}
195
- *//**
196
- * Set SAMPLE settings.
197
- * @param {number|object} value The percentage or number of rows to sample.
198
- * @param {string} [method] The sampling method to use.
199
- * @returns {this}
200
- */
201
- sample(value, method) {
202
- const { query } = this;
203
- if (arguments.length === 0) {
204
- // @ts-ignore
205
- return query.sample;
206
- } else {
207
- let spec = value;
208
- if (typeof value === 'number') {
209
- spec = value > 0 && value < 1
210
- ? { perc: 100 * value, method }
211
- : { rows: Math.round(value), method };
212
- }
213
- query.sample = spec;
214
- return this;
215
- }
216
- }
217
-
218
- /**
219
- * Retrieve current WHERE expressions.
220
- * @returns {any[]}
221
- *//**
222
- * Add WHERE expressions.
223
- * @param {...any} expr Expressions to add.
224
- * @returns {this}
225
- */
226
- where(...expr) {
227
- const { query } = this;
228
- if (expr.length === 0) {
229
- // @ts-ignore
230
- return query.where;
231
- } else {
232
- query.where = query.where.concat(
233
- expr.flat().filter(x => x)
234
- );
235
- return this;
236
- }
237
- }
238
-
239
- $where(...expr) {
240
- this.query.where = [];
241
- return this.where(...expr);
242
- }
243
-
244
- /**
245
- * Retrieve current GROUP BY expressions.
246
- * @returns {any[]}
247
- *//**
248
- * Add GROUP BY expressions.
249
- * @param {...any} expr Expressions to add.
250
- * @returns {this}
251
- */
252
- groupby(...expr) {
253
- const { query } = this;
254
- if (expr.length === 0) {
255
- // @ts-ignore
256
- return query.groupby;
257
- } else {
258
- query.groupby = query.groupby.concat(
259
- expr.flat().filter(x => x).map(asColumn)
260
- );
261
- return this;
262
- }
263
- }
264
-
265
- $groupby(...expr) {
266
- this.query.groupby = [];
267
- return this.groupby(...expr);
268
- }
269
-
270
- /**
271
- * Retrieve current HAVING expressions.
272
- * @returns {any[]}
273
- *//**
274
- * Add HAVING expressions.
275
- * @param {...any} expr Expressions to add.
276
- * @returns {this}
277
- */
278
- having(...expr) {
279
- const { query } = this;
280
- if (expr.length === 0) {
281
- // @ts-ignore
282
- return query.having;
283
- } else {
284
- query.having = query.having.concat(
285
- expr.flat().filter(x => x)
286
- );
287
- return this;
288
- }
289
- }
290
-
291
- /**
292
- * Retrieve current WINDOW definitions.
293
- * @returns {any[]}
294
- *//**
295
- * Add WINDOW definitions.
296
- * @param {...any} expr Expressions to add.
297
- * @returns {this}
298
- */
299
- window(...expr) {
300
- const { query } = this;
301
- if (expr.length === 0) {
302
- // @ts-ignore
303
- return query.window;
304
- } else {
305
- const list = [];
306
- expr.flat().forEach(e => {
307
- if (e == null) {
308
- // do nothing
309
- } else {
310
- for (const as in e) {
311
- list.push({ as: unquote(as), expr: e[as] });
312
- }
313
- }
314
- });
315
- query.window = query.window.concat(list);
316
- return this;
317
- }
318
- }
319
-
320
- /**
321
- * Retrieve current QUALIFY expressions.
322
- * @returns {any[]}
323
- *//**
324
- * Add QUALIFY expressions.
325
- * @param {...any} expr Expressions to add.
326
- * @returns {this}
327
- */
328
- qualify(...expr) {
329
- const { query } = this;
330
- if (expr.length === 0) {
331
- // @ts-ignore
332
- return query.qualify;
333
- } else {
334
- query.qualify = query.qualify.concat(
335
- expr.flat().filter(x => x)
336
- );
337
- return this;
338
- }
339
- }
340
-
341
- /**
342
- * Retrieve current ORDER BY expressions.
343
- * @returns {any[]}
344
- *//**
345
- * Add ORDER BY expressions.
346
- * @param {...any} expr Expressions to add.
347
- * @returns {this}
348
- */
349
- orderby(...expr) {
350
- const { query } = this;
351
- if (expr.length === 0) {
352
- // @ts-ignore
353
- return query.orderby;
354
- } else {
355
- query.orderby = query.orderby.concat(
356
- expr.flat().filter(x => x).map(asColumn)
357
- );
358
- return this;
359
- }
360
- }
361
-
362
- /**
363
- * Retrieve current LIMIT value.
364
- * @returns {number|null}
365
- *//**
366
- * Set the query result LIMIT.
367
- * @param {number} value The limit value.
368
- * @returns {this}
369
- */
370
- limit(value) {
371
- const { query } = this;
372
- if (arguments.length === 0) {
373
- return query.limit;
374
- } else {
375
- query.limit = Number.isFinite(value) ? value : undefined;
376
- return this;
377
- }
378
- }
379
-
380
- /**
381
- * Retrieve current OFFSET value.
382
- * @returns {number|null}
383
- *//**
384
- * Set the query result OFFSET.
385
- * @param {number} value The offset value.
386
- * @returns {this}
387
- */
388
- offset(value) {
389
- const { query } = this;
390
- if (arguments.length === 0) {
391
- return query.offset;
392
- } else {
393
- query.offset = Number.isFinite(value) ? value : undefined;
394
- return this;
395
- }
396
- }
397
-
398
- get subqueries() {
399
- const { query, cteFor } = this;
400
- const ctes = (cteFor?.query || query).with;
401
- const cte = ctes?.reduce((o, {as, query}) => (o[as] = query, o), {});
402
- const q = [];
403
- query.from.forEach(({ from }) => {
404
- if (isQuery(from)) {
405
- q.push(from);
406
- } else if (cte[from.table]) {
407
- const sub = cte[from.table];
408
- q.push(sub);
409
- }
410
- });
411
- return q;
412
- }
413
-
414
- toString() {
415
- const {
416
- with: cte, select, distinct, from, sample, where, groupby,
417
- having, window, qualify, orderby, limit, offset
418
- } = this.query;
419
-
420
- const sql = [];
421
-
422
- // WITH
423
- if (cte.length) {
424
- const list = cte.map(({ as, query })=> `"${as}" AS (${query})`);
425
- sql.push(`WITH ${list.join(', ')}`);
426
- }
427
-
428
- // SELECT
429
- const sels = select.map(
430
- ({ as, expr }) => isColumnRefFor(expr, as) && !expr.table
431
- ? `${expr}`
432
- : `${expr} AS "${as}"`
433
- );
434
- sql.push(`SELECT${distinct ? ' DISTINCT' : ''} ${sels.join(', ')}`);
435
-
436
- // FROM
437
- if (from.length) {
438
- const rels = from.map(({ as, from }) => {
439
- const rel = isQuery(from) ? `(${from})` : `${from}`;
440
- return !as || as === from.table ? rel : `${rel} AS "${as}"`;
441
- });
442
- sql.push(`FROM ${rels.join(', ')}`);
443
- }
444
-
445
- // WHERE
446
- if (where.length) {
447
- const clauses = where.map(String).filter(x => x).join(' AND ');
448
- if (clauses) sql.push(`WHERE ${clauses}`);
449
- }
450
-
451
- // SAMPLE
452
- if (sample) {
453
- const { rows, perc, method, seed } = sample;
454
- const size = rows ? `${rows} ROWS` : `${perc} PERCENT`;
455
- const how = method ? ` (${method}${seed != null ? `, ${seed}` : ''})` : '';
456
- sql.push(`USING SAMPLE ${size}${how}`);
457
- }
458
-
459
- // GROUP BY
460
- if (groupby.length) {
461
- sql.push(`GROUP BY ${groupby.join(', ')}`);
462
- }
463
-
464
- // HAVING
465
- if (having.length) {
466
- const clauses = having.map(String).filter(x => x).join(' AND ');
467
- if (clauses) sql.push(`HAVING ${clauses}`);
468
- }
469
-
470
- // WINDOW
471
- if (window.length) {
472
- const windows = window.map(({ as, expr }) => `"${as}" AS (${expr})`);
473
- sql.push(`WINDOW ${windows.join(', ')}`);
474
- }
475
-
476
- // QUALIFY
477
- if (qualify.length) {
478
- const clauses = qualify.map(String).filter(x => x).join(' AND ');
479
- if (clauses) sql.push(`QUALIFY ${clauses}`);
480
- }
481
-
482
- // ORDER BY
483
- if (orderby.length) {
484
- sql.push(`ORDER BY ${orderby.join(', ')}`);
485
- }
486
-
487
- // LIMIT
488
- if (Number.isFinite(limit)) {
489
- sql.push(`LIMIT ${limit}`);
490
- }
491
-
492
- // OFFSET
493
- if (Number.isFinite(offset)) {
494
- sql.push(`OFFSET ${offset}`);
495
- }
496
-
497
- return sql.join(' ');
498
- }
499
- }
500
-
501
- export class SetOperation {
502
- constructor(op, queries) {
503
- this.op = op;
504
- this.queries = queries.map(q => q.clone());
505
- this.query = { orderby: [] };
506
- this.cteFor = null;
507
- }
508
-
509
- clone() {
510
- const q = new SetOperation(this.op, this.queries);
511
- q.query = { ...this.query };
512
- return q;
513
- }
514
-
515
- orderby(...expr) {
516
- const { query } = this;
517
- if (expr.length === 0) {
518
- return query.orderby;
519
- } else {
520
- query.orderby = query.orderby.concat(
521
- expr.flat().filter(x => x).map(asColumn)
522
- );
523
- return this;
524
- }
525
- }
526
-
527
- limit(value) {
528
- const { query } = this;
529
- if (arguments.length === 0) {
530
- return query.limit;
531
- } else {
532
- query.limit = Number.isFinite(value) ? value : undefined;
533
- return this;
534
- }
535
- }
536
-
537
- offset(value) {
538
- const { query } = this;
539
- if (arguments.length === 0) {
540
- return query.offset;
541
- } else {
542
- query.offset = Number.isFinite(value) ? value : undefined;
543
- return this;
544
- }
545
- }
546
-
547
- get subqueries() {
548
- const { queries, cteFor } = this;
549
- if (cteFor) queries.forEach(q => q.cteFor = cteFor);
550
- return queries;
551
- }
552
-
553
- toString() {
554
- const { op, queries, query: { orderby, limit, offset } } = this;
555
-
556
- // SUBQUERIES
557
- const sql = [ queries.join(` ${op} `) ];
558
-
559
- // ORDER BY
560
- if (orderby.length) {
561
- sql.push(`ORDER BY ${orderby.join(', ')}`);
562
- }
563
-
564
- // LIMIT
565
- if (Number.isFinite(limit)) {
566
- sql.push(`LIMIT ${limit}`);
567
- }
568
-
569
- // OFFSET
570
- if (Number.isFinite(offset)) {
571
- sql.push(`OFFSET ${offset}`);
572
- }
573
-
574
- return sql.join(' ');
575
- }
576
- }
577
-
578
- export function isQuery(value) {
579
- return value instanceof Query || value instanceof SetOperation;
580
- }
581
-
582
- export function isDescribeQuery(value) {
583
- // @ts-ignore
584
- return isQuery(value) && value.describe;
585
- }
586
-
587
- function unquote(s) {
588
- return isDoubleQuoted(s) ? s.slice(1, -1) : s;
589
- }
590
-
591
- function isDoubleQuoted(s) {
592
- return s[0] === '"' && s[s.length-1] === '"';
593
- }