Copy link to clipboard
Copied
ExtendScript does not handle the continue statement according to the ECMAScript 3rd Edition standard when located within a case clause of a switch statement. The problem is slightly different when a continue statement is placed within a switch statement's default clause. The specific issues are as follows:
This can cause fairly straightforward logic to explode in surprising ways. It can also make it difficult to use modern technologies with ExtendScript (e.g. transpilers).
This is a simple test-case:
var test = function()
{
for (var i = 0; i < 3; ++i)
{
$.writeln("Pre Switch");
switch(i)
{
case 0:
$.writeln("\tCase 0");
break;
case 1:
$.writeln("\tCase 1");
continue;
default:
$.writeln("\tStart Default");
if (i == 2)
{
$.writeln("\t\tAt 2");
continue;
}
$.writeln("\tEnd Default");
}
$.writeln("Post Switch");
}
}
test();
Running this in the ExtendScript Toolkit (v4.0.0.1, ExtendScript 4.5.5) produces the following output:
Pre Switch
Case 0
Post Switch
Pre Switch
Case 1
Start Default
End Default
Post Switch
Pre Switch
Start Default
At 2
Post Switch
Lines that should not be printed are highlighted in red. There are two groupings of incorrect functionality, one for each of the two continue statements. Explanations of what is happening in these two cases follows:
This is the expected output:
Pre Switch
Case 0
Post Switch
Pre Switch
Case 1
Pre Switch
Start Default
At 2
That output was generated in the JavaScript console of the Chrome 64 browser. It is possible to run the test code provided above​ in a browser context as long as you first run the following:
var $ = {
writeln: console.log
};
This creates a $ object with a writeln function that simply points to the standard console.log function. Once this has been processed in the browser's JavaScript context, the test script provided will run without issue.
According to the ECMAScript 3rd Edition spec [p. 67], continue statements always refer to "Iteration Statements" (for, while, do-while loops). They should ignore switch statements entirely.
Copy link to clipboard
Copied
Just don't use Switch. I don't say that as a lazy answer but as a generic good practice in ExtendScript (performance issues If I recall well). It's likely that you will always have a way not to use it.
But sure, bugs can be.
Copy link to clipboard
Copied
Loic.Aigon wrote
Just don't use Switch. I don't say that as a lazy answer but as a generic good practice in ExtendScript (performance issues If I recall well). It's likely that you will always have a way not to use it.
Of course. You can always use an extended if-else tree instead (although fall-through cases are a bit less clear).
That said, people are resorting more and more to using third-party JavaScript code. One major example is JSON​, which Adobe themselves use in their internal panels. I've checked JSON2 and JSON3 and neither of those contain the continue statement. But this does not hold true for all code out there.
One more complex example is that of TypeScript, which allows you to use modern programming practices through syntactic downleveling​ (e.g. async functions and generators/iteration). TypeScript can emit ECMAScript 3 valid code even though it uses ECMAScript 5+ features. Unfortunately, transpiled generator code makes use of the continue statement which completely breaks the feature in today's ExtendScript.
Copy link to clipboard
Copied
The "transpiler" link in my original post predates the ability to share links with TSConfig settings properly communicated. The current TypeScript Playground now includes that setting. This newer link shows the problematic output.
In short, it converts this:
function* idMaker() {
let index = 0;
while(index < 3)
yield index++;
}
let gen = idMaker();
$.writeln(gen.next()); // { value: 0, done: false }
$.writeln(gen.next()); // { value: 1, done: false }
$.writeln(gen.next()); // { value: 2, done: false }
$.writeln(gen.next()); // { done: true }
into this:
"use strict";
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
function idMaker() {
var index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
index = 0;
_a.label = 1;
case 1:
if (!(index < 3)) return [3 /*break*/, 3];
return [4 /*yield*/, index++];
case 2:
_a.sent();
return [3 /*break*/, 1];
case 3: return [2 /*return*/];
}
});
}
var gen = idMaker();
$.writeln(gen.next()); // { value: 0, done: false }
$.writeln(gen.next()); // { value: 1, done: false }
$.writeln(gen.next()); // { value: 2, done: false }
$.writeln(gen.next()); // { done: true }