pxt-core 7.3.21 → 7.3.25

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.
@@ -471,7 +471,7 @@ declare namespace pxtblockly {
471
471
  protected animateRef: any;
472
472
  protected asset: pxt.Animation;
473
473
  protected initInterval: number;
474
- initView(): void;
474
+ init(): void;
475
475
  showEditor_(): void;
476
476
  render_(): void;
477
477
  protected getAssetType(): pxt.AssetType;
@@ -508,7 +508,7 @@ declare namespace pxtblockly {
508
508
  CURSOR: string;
509
509
  private type_;
510
510
  constructor(state: string, params: Blockly.FieldCustomOptions, opt_validator?: Function);
511
- initView(): void;
511
+ init(): void;
512
512
  updateSize_(): void;
513
513
  /**
514
514
  * Return 'TRUE' if the toggle is ON, 'FALSE' otherwise.
@@ -1243,7 +1243,7 @@ declare namespace pxtblockly {
1243
1243
  protected blocksInfo: pxtc.BlocksInfo;
1244
1244
  protected transparent: TilesetDropdownOption;
1245
1245
  constructor(text: string, options: FieldImageDropdownOptions, validator?: Function);
1246
- initView(): void;
1246
+ init(): void;
1247
1247
  getValue(): any;
1248
1248
  getText(): string;
1249
1249
  render_(): void;
@@ -1265,7 +1265,7 @@ declare namespace pxtblockly {
1265
1265
  CURSOR: string;
1266
1266
  private type_;
1267
1267
  constructor(state: string, params: Blockly.FieldCustomOptions, opt_validator?: Function);
1268
- initView(): void;
1268
+ init(): void;
1269
1269
  getDisplayText_(): string;
1270
1270
  getTrueText(): string;
1271
1271
  getFalseText(): string;
@@ -1497,7 +1497,7 @@ var pxt;
1497
1497
  function compileBlockAsync(b, blockInfo) {
1498
1498
  const w = b.workspace;
1499
1499
  const e = mkEnv(w, blockInfo);
1500
- infer(w && w.getAllBlocks(false), e, w);
1500
+ infer(w && w.getAllBlocks(), e, w);
1501
1501
  const compiled = compileStatementBlock(e, b);
1502
1502
  removeAllPlaceholders();
1503
1503
  return tdASTtoTS(e, compiled);
@@ -1518,7 +1518,7 @@ var pxt;
1518
1518
  function compileWorkspace(e, w, blockInfo) {
1519
1519
  try {
1520
1520
  // all compiled top level blocks are events
1521
- let allBlocks = w.getAllBlocks(false);
1521
+ let allBlocks = w.getAllBlocks();
1522
1522
  if (pxt.react.getTilemapProject) {
1523
1523
  pxt.react.getTilemapProject().removeInactiveBlockAssets(allBlocks.map(b => b.id));
1524
1524
  }
@@ -1788,11 +1788,13 @@ var pxt;
1788
1788
  });
1789
1789
  }
1790
1790
  function maybeAddComment(b, comments) {
1791
- var _a;
1792
- // Check if getCommentText exists, block may be placeholder
1793
- const text = (_a = b.getCommentText) === null || _a === void 0 ? void 0 : _a.call(b);
1794
- if (text) {
1795
- comments.push(text);
1791
+ if (b.comment) {
1792
+ if ((typeof b.comment) === "string") {
1793
+ comments.push(b.comment);
1794
+ }
1795
+ else {
1796
+ comments.push(b.comment.getText());
1797
+ }
1796
1798
  }
1797
1799
  }
1798
1800
  function addCommentNodes(comments, r) {
@@ -2455,9 +2457,9 @@ var pxt;
2455
2457
  }
2456
2458
  });
2457
2459
  // we'll ignore disabled blocks in the final output
2458
- const oldBlocks = oldWs.getAllBlocks(false).filter(b => b.isEnabled());
2460
+ const oldBlocks = oldWs.getAllBlocks().filter(b => b.isEnabled());
2459
2461
  const oldTopBlocks = oldWs.getTopBlocks(false).filter(b => b.isEnabled());
2460
- const newBlocks = newWs.getAllBlocks(false).filter(b => b.isEnabled());
2462
+ const newBlocks = newWs.getAllBlocks().filter(b => b.isEnabled());
2461
2463
  log(`blocks`, newBlocks.map(b => b.toDevString()));
2462
2464
  log(newBlocks);
2463
2465
  if (oldBlocks.length == 0 && newBlocks.length == 0) {
@@ -2479,11 +2481,11 @@ var pxt;
2479
2481
  const newXml = pxt.blocks.saveWorkspaceXml(newWs, true);
2480
2482
  pxt.blocks.domToWorkspaceNoEvents(Blockly.Xml.textToDom(newXml), ws);
2481
2483
  // delete disabled blocks from final workspace
2482
- ws.getAllBlocks(false).filter(b => !b.isEnabled()).forEach(b => {
2484
+ ws.getAllBlocks().filter(b => !b.isEnabled()).forEach(b => {
2483
2485
  log('disabled ', b.toDevString());
2484
2486
  b.dispose(false);
2485
2487
  });
2486
- const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(false), b => b.id);
2488
+ const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(), b => b.id);
2487
2489
  log(`todo blocks`, todoBlocks);
2488
2490
  logTodo('start');
2489
2491
  // 1. deleted top blocks
@@ -2493,7 +2495,7 @@ var pxt;
2493
2495
  done(b);
2494
2496
  const b2 = cloneIntoDiff(b);
2495
2497
  done(b2);
2496
- b2.setEnabled(false);
2498
+ b2.setDisabled(true);
2497
2499
  });
2498
2500
  logTodo('deleted top');
2499
2501
  }
@@ -2558,7 +2560,7 @@ var pxt;
2558
2560
  });
2559
2561
  logTodo('unmodified');
2560
2562
  // if nothing is left in the workspace, we "missed" change
2561
- if (!ws.getAllBlocks(false).length) {
2563
+ if (!ws.getAllBlocks().length) {
2562
2564
  pxt.tickEvent("blocks.diff", { missed: 1 });
2563
2565
  return {
2564
2566
  ws,
@@ -2591,7 +2593,7 @@ var pxt;
2591
2593
  function stitch(b) {
2592
2594
  log(`stitching ${b.toDevString()}->${dids[b.id]}`);
2593
2595
  const wb = ws.getBlockById(dids[b.id]);
2594
- wb.setEnabled(false);
2596
+ wb.setDisabled(true);
2595
2597
  markUsed(wb);
2596
2598
  done(wb);
2597
2599
  // connect previous connection to delted or existing block
@@ -2853,10 +2855,10 @@ var pxt;
2853
2855
  function applyMetaComments(workspace) {
2854
2856
  // process meta comments
2855
2857
  // @highlight -> highlight block
2856
- workspace.getAllBlocks(false)
2857
- .filter(b => !!b.getCommentText())
2858
+ workspace.getAllBlocks()
2859
+ .filter(b => !!b.comment && b.comment instanceof Blockly.Comment)
2858
2860
  .forEach(b => {
2859
- const c = b.getCommentText();
2861
+ const c = b.comment.getText();
2860
2862
  if (/@highlight/.test(c)) {
2861
2863
  const cc = c.replace(/@highlight/g, '').trim();
2862
2864
  b.setCommentText(cc || null);
@@ -4341,7 +4343,7 @@ var pxt;
4341
4343
  }
4342
4344
  else if (pr.type == "number" && pr.shadowBlockId && pr.shadowBlockId == "value") {
4343
4345
  inputName = undefined;
4344
- fields.push(namedField(new Blockly.FieldNumber("0"), defName));
4346
+ fields.push(namedField(new Blockly.FieldTextInput("0", Blockly.FieldTextInput.numberValidator), defName));
4345
4347
  }
4346
4348
  else if (pr.type == "string" && pr.shadowOptions && pr.shadowOptions.toString) {
4347
4349
  inputCheck = null;
@@ -5443,6 +5445,11 @@ var pxt;
5443
5445
  let blockText = '<xml>' +
5444
5446
  '<block type="variables_change" gap="' + gap + '">' +
5445
5447
  Blockly.Variables.generateVariableFieldXmlString(mostRecentVariable) +
5448
+ '<value name="DELTA">' +
5449
+ '<shadow type="math_number">' +
5450
+ '<field name="NUM">1</field>' +
5451
+ '</shadow>' +
5452
+ '</value>' +
5446
5453
  '</block>' +
5447
5454
  '</xml>';
5448
5455
  let block = Blockly.Xml.textToDom(blockText).firstChild;
@@ -7615,8 +7622,19 @@ var pxtblockly;
7615
7622
  this.valueText = text;
7616
7623
  }
7617
7624
  init() {
7618
- super.init();
7625
+ if (this.isInitialized())
7626
+ return;
7627
+ // Build the DOM.
7628
+ this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
7629
+ if (!this.visible_) {
7630
+ this.fieldGroup_.style.display = 'none';
7631
+ }
7619
7632
  this.onInit();
7633
+ this.updateEditable();
7634
+ this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
7635
+ // Force a render.
7636
+ this.render_();
7637
+ this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(this.getClickTarget_(), "mousedown", this, this.onMouseDown_);
7620
7638
  }
7621
7639
  dispose() {
7622
7640
  this.onDispose();
@@ -7769,11 +7787,9 @@ var pxtblockly;
7769
7787
  }
7770
7788
  getDisplayText_() {
7771
7789
  // This is only used when isGreyBlock is true
7772
- if (this.isGreyBlock) {
7773
- const text = pxt.Util.htmlUnescape(this.valueText);
7774
- return text.substr(0, text.indexOf("(")) + "(...)";
7775
- }
7776
- return "";
7790
+ const text = pxt.Util.htmlUnescape(this.valueText);
7791
+ return text.substr(0, text.indexOf("(")) + "(...)";
7792
+ ;
7777
7793
  }
7778
7794
  updateEditable() {
7779
7795
  if (this.isGreyBlock && this.fieldGroup_) {
@@ -8030,7 +8046,12 @@ var pxtblockly;
8030
8046
  }
8031
8047
  };
8032
8048
  }
8033
- initView() {
8049
+ init() {
8050
+ if (this.fieldGroup_) {
8051
+ // Field has already been initialized once.
8052
+ return;
8053
+ }
8054
+ super.init();
8034
8055
  // Register mouseover events for animating preview
8035
8056
  this.sourceBlock_.getSvgRoot().addEventListener("mouseenter", this.onMouseEnter);
8036
8057
  this.sourceBlock_.getSvgRoot().addEventListener("mouseleave", this.onMouseLeave);
@@ -8236,10 +8257,16 @@ var pxtblockly;
8236
8257
  this.addArgType('toggle');
8237
8258
  this.type_ = params.type;
8238
8259
  }
8239
- initView() {
8240
- if (!this.fieldGroup_) {
8260
+ init() {
8261
+ if (this.fieldGroup_) {
8262
+ // Field has already been initialized once.
8241
8263
  return;
8242
8264
  }
8265
+ // Build the DOM.
8266
+ this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
8267
+ if (!this.visible_) {
8268
+ this.fieldGroup_.style.display = 'none';
8269
+ }
8243
8270
  // Add an attribute to cassify the type of field.
8244
8271
  if (this.getArgTypes() !== null) {
8245
8272
  if (this.sourceBlock_.isShadow()) {
@@ -8268,10 +8295,15 @@ var pxtblockly;
8268
8295
  'dy': '0.6ex',
8269
8296
  'y': size.height / 2
8270
8297
  }, this.fieldGroup_);
8298
+ this.updateEditable();
8299
+ this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
8271
8300
  this.switchToggle(this.state_);
8272
8301
  this.setValue(this.getValue());
8273
8302
  // Force a render.
8274
- this.markDirty();
8303
+ this.render_();
8304
+ this.size_.width = 0;
8305
+ this.mouseDownWrapper_ =
8306
+ Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
8275
8307
  }
8276
8308
  updateSize_() {
8277
8309
  this.size_.width = 30;
@@ -9741,7 +9773,7 @@ var pxtblockly;
9741
9773
  }
9742
9774
  render_() {
9743
9775
  if (!this.visible_) {
9744
- this.markDirty();
9776
+ this.size_.width = 0;
9745
9777
  return;
9746
9778
  }
9747
9779
  if (!this.elt) {
@@ -11245,7 +11277,7 @@ var pxtblockly;
11245
11277
  dropdownCreate() {
11246
11278
  let functionList = [];
11247
11279
  if (this.sourceBlock_ && this.sourceBlock_.workspace) {
11248
- let blocks = this.sourceBlock_.workspace.getAllBlocks(false);
11280
+ let blocks = this.sourceBlock_.workspace.getAllBlocks();
11249
11281
  // Iterate through every block and check the name.
11250
11282
  for (let i = 0; i < blocks.length; i++) {
11251
11283
  if (blocks[i].getProcedureDef) {
@@ -11274,6 +11306,10 @@ var pxtblockly;
11274
11306
  }
11275
11307
  onItemSelected(menu, menuItem) {
11276
11308
  let itemText = menuItem.getValue();
11309
+ if (this.sourceBlock_) {
11310
+ // Call any validation function, and allow it to override.
11311
+ itemText = this.callValidator(itemText);
11312
+ }
11277
11313
  if (itemText !== null) {
11278
11314
  this.setValue(itemText);
11279
11315
  }
@@ -11768,8 +11804,8 @@ var pxtblockly;
11768
11804
  }
11769
11805
  return FieldTileset.referencedTiles;
11770
11806
  }
11771
- initView() {
11772
- super.initView();
11807
+ init() {
11808
+ super.init();
11773
11809
  if (this.sourceBlock_ && this.sourceBlock_.isInFlyout) {
11774
11810
  this.setValue(this.getOptions()[0][1]);
11775
11811
  }
@@ -11918,10 +11954,16 @@ var pxtblockly;
11918
11954
  this.addArgType('toggle');
11919
11955
  this.type_ = params.type;
11920
11956
  }
11921
- initView() {
11922
- if (!this.fieldGroup_) {
11957
+ init() {
11958
+ if (this.fieldGroup_) {
11959
+ // Field has already been initialized once.
11923
11960
  return;
11924
11961
  }
11962
+ // Build the DOM.
11963
+ this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
11964
+ if (!this.visible_) {
11965
+ this.fieldGroup_.style.display = 'none';
11966
+ }
11925
11967
  // Add an attribute to cassify the type of field.
11926
11968
  if (this.getArgTypes() !== null) {
11927
11969
  if (this.sourceBlock_.isShadow()) {
@@ -11993,7 +12035,10 @@ var pxtblockly;
11993
12035
  this.switchToggle(this.state_);
11994
12036
  this.setValue(this.getValue());
11995
12037
  // Force a render.
11996
- this.markDirty();
12038
+ this.render_();
12039
+ this.size_.width = 0;
12040
+ this.mouseDownWrapper_ =
12041
+ Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
11997
12042
  }
11998
12043
  getDisplayText_() {
11999
12044
  return this.state_ ? this.getTrueText() : this.getFalseText();
package/built/pxtlib.d.ts CHANGED
@@ -129,8 +129,11 @@ declare namespace pxt.auth {
129
129
  username?: string;
130
130
  avatarUrl?: string;
131
131
  }): Promise<boolean>;
132
- private prefPatchOps;
133
- patchUserPreferencesAsync(ops: ts.pxtc.jsonPatch.PatchOperation | ts.pxtc.jsonPatch.PatchOperation[], immediate?: boolean): Promise<SetPrefResult>;
132
+ private patchQueue;
133
+ patchUserPreferencesAsync(patchOps: ts.pxtc.jsonPatch.PatchOperation | ts.pxtc.jsonPatch.PatchOperation[], opts?: {
134
+ immediate?: boolean;
135
+ filter?: (op: ts.pxtc.jsonPatch.PatchOperation) => boolean;
136
+ }): Promise<SetPrefResult>;
134
137
  hasUserId(): boolean;
135
138
  private fetchUserAsync;
136
139
  private setUserProfileAsync;
@@ -302,6 +305,7 @@ declare namespace ts.pxtc.Util {
302
305
  export function groupBy<T>(arr: T[], f: (t: T) => string): pxt.Map<T[]>;
303
306
  export function toDictionary<T>(arr: T[], f: (t: T) => string): pxt.Map<T>;
304
307
  export function toSet<T>(arr: T[], f: (t: T) => string): pxt.Map<boolean>;
308
+ export function deepCopy(src: any): any;
305
309
  export interface ArrayLike<T> {
306
310
  [index: number]: T;
307
311
  length: number;
package/built/pxtlib.js CHANGED
@@ -199,7 +199,7 @@ var pxt;
199
199
  constructor() {
200
200
  this.initialUserPreferences_ = undefined;
201
201
  this.initialAuthCheck_ = undefined;
202
- this.prefPatchOps = [];
202
+ this.patchQueue = [];
203
203
  pxt.Util.assert(!_client);
204
204
  // Set global instance.
205
205
  _client = this;
@@ -389,63 +389,85 @@ var pxt;
389
389
  }
390
390
  return result.success;
391
391
  }
392
- async patchUserPreferencesAsync(ops, immediate = false) {
393
- ops = Array.isArray(ops) ? ops : [ops];
394
- ops = ops.filter(op => !!op);
392
+ async patchUserPreferencesAsync(patchOps, opts = {}) {
393
+ const defaultSuccessAsync = async () => ({ success: true, res: await this.userPreferencesAsync() });
394
+ patchOps = Array.isArray(patchOps) ? patchOps : [patchOps];
395
+ patchOps = patchOps.filter(op => !!op);
396
+ if (!patchOps.length) {
397
+ return await defaultSuccessAsync();
398
+ }
399
+ const patchDiff = (pSrc, ops, filter) => {
400
+ // Apply patches to pDst and return the diff as a set of new patch ops.
401
+ const pDst = pxt.U.deepCopy(pSrc);
402
+ ts.pxtc.jsonPatch.patchInPlace(pDst, ops);
403
+ let diff = ts.pxtc.jsonPatch.diff(pSrc, pDst);
404
+ // Run caller-provided filter
405
+ if (diff.length && filter) {
406
+ diff = diff.filter(filter);
407
+ }
408
+ return diff;
409
+ };
410
+ // Process incoming patch operations to produce a more fine-grained set of diffs. Incoming patches may be overly destructive
411
+ // Apply the patch in isolation and get the diff from original
395
412
  const curPref = await this.userPreferencesAsync();
396
- if (!ops.length) {
397
- return { success: true, res: curPref };
413
+ const diff = patchDiff(curPref, patchOps, opts.filter);
414
+ if (!diff.length) {
415
+ return await defaultSuccessAsync();
398
416
  }
399
- ts.pxtc.jsonPatch.patchInPlace(curPref, ops);
417
+ // Apply the new diff to the current state
418
+ ts.pxtc.jsonPatch.patchInPlace(curPref, diff);
400
419
  await this.setUserPreferencesAsync(curPref);
401
- // If we're not logged in, non-persistent local state is all we'll use
420
+ // If the user is not logged in, non-persistent local state is all we'll use (no sync to cloud)
402
421
  if (!await this.loggedInAsync()) {
403
- return { success: true, res: curPref };
422
+ return await defaultSuccessAsync();
404
423
  }
405
- // If the user is logged in, save to cloud, but debounce the api call as this can be called frequently from skillmaps
406
- // Replace matching patches in the queue
407
- ops.forEach((incoming, iIncoming) => {
408
- this.prefPatchOps.some((existing, iExisting) => {
409
- if (!ts.pxtc.jsonPatch.opsAreEqual(existing, incoming))
410
- return false;
411
- // Patches are equivalent, replace in queue
412
- this.prefPatchOps[iExisting] = incoming;
413
- // Clear from incoming so we don't add it below
414
- ops[iIncoming] = null;
415
- return true;
416
- });
417
- });
418
- // Add remaining ops to the queue
419
- ops.filter(op => !!op).forEach(op => this.prefPatchOps.push(op));
424
+ // If the user is logged in, sync to cloud, but debounce the api call as this can be called frequently from skillmaps
425
+ // Queue the patch for sync with backend
426
+ this.patchQueue.push({ ops: patchOps, filter: opts.filter });
420
427
  clearTimeout(debouncePreferencesChangedTimeout);
421
- const savePrefs = async () => {
428
+ const syncPrefs = async () => {
422
429
  debouncePreferencesChangedStarted = 0;
423
- // Clear queued patch ops before send.
424
- const prefPatchOps = this.prefPatchOps;
425
- this.prefPatchOps = [];
426
- const result = await this.apiAsync('/api/user/preferences', prefPatchOps, 'PATCH');
427
- if (result.success) {
428
- pxt.debug("Updating local user preferences w/ cloud data after result of POST");
429
- // Set user profile from returned value so we stay in-sync
430
- this.setUserPreferencesAsync(result.resp);
430
+ if (!this.patchQueue.length) {
431
+ return await defaultSuccessAsync();
432
+ }
433
+ // Fetch latest prefs from remote
434
+ const getResult = await this.apiAsync('/api/user/preferences');
435
+ if (!getResult.success) {
436
+ pxt.reportError("identity", "failed to fetch preferences for patch", getResult);
437
+ return { success: false, res: undefined };
438
+ }
439
+ // Apply queued patches to the remote state in isolation and develop a final diff to send to the backend
440
+ const remotePrefs = pxt.U.deepCopy(getResult.resp);
441
+ const patchQueue = this.patchQueue;
442
+ this.patchQueue = []; // Reset the queue
443
+ patchQueue.forEach(patch => {
444
+ const diff = patchDiff(remotePrefs, patch.ops, patch.filter);
445
+ ts.pxtc.jsonPatch.patchInPlace(remotePrefs, diff);
446
+ });
447
+ // Diff the original and patched remote states to get a final set of patch operations
448
+ const finalOps = pxtc.jsonPatch.diff(getResult.resp, remotePrefs);
449
+ const patchResult = await this.apiAsync('/api/user/preferences', finalOps, 'PATCH');
450
+ if (patchResult.success) {
451
+ // Set user profile from returned value so we stay in sync
452
+ this.setUserPreferencesAsync(patchResult.resp);
431
453
  }
432
454
  else {
433
- pxt.reportError("identity", "update preferences failed", result);
455
+ pxt.reportError("identity", "failed to patch preferences", patchResult);
434
456
  }
435
- return { success: result.success, res: result.resp };
457
+ return { success: patchResult.success, res: patchResult.resp };
436
458
  };
437
- if (immediate) {
438
- return await savePrefs();
459
+ if (opts.immediate) {
460
+ return await syncPrefs();
439
461
  }
440
462
  else {
441
463
  if (!debouncePreferencesChangedStarted) {
442
464
  debouncePreferencesChangedStarted = pxt.U.now();
443
465
  }
444
466
  if (PREFERENCES_DEBOUNCE_MAX_MS < pxt.U.now() - debouncePreferencesChangedStarted) {
445
- return await savePrefs();
467
+ return await syncPrefs();
446
468
  }
447
469
  else {
448
- debouncePreferencesChangedTimeout = setTimeout(savePrefs, PREFERENCES_DEBOUNCE_MS);
470
+ debouncePreferencesChangedTimeout = setTimeout(syncPrefs, PREFERENCES_DEBOUNCE_MS);
449
471
  return { success: false, res: undefined }; // This needs to be implemented correctly to return a promise with the debouncer
450
472
  }
451
473
  }
@@ -1528,6 +1550,18 @@ var ts;
1528
1550
  return r;
1529
1551
  }
1530
1552
  Util.toSet = toSet;
1553
+ function deepCopy(src) {
1554
+ if (typeof src !== "object" || src === null) {
1555
+ return src;
1556
+ }
1557
+ const dst = Array.isArray(src) ? [] : {};
1558
+ for (const key in src) {
1559
+ const value = src[key];
1560
+ dst[key] = deepCopy(value);
1561
+ }
1562
+ return dst;
1563
+ }
1564
+ Util.deepCopy = deepCopy;
1531
1565
  function toArray(a) {
1532
1566
  if (Array.isArray(a)) {
1533
1567
  return a;