CHANGES.md
CHANGES.md lists intentional changes between the Strada (TypeScript) and Corsa (Go) compilers.
At a high level, JavaScript support in Corsa is intended to expose TypeScript features in a .js file, working exactly as they do in TypeScript with different syntax.
This differs from Strada, which has many JavaScript features that do not exist in TypeScript at all, and quite a few differences in features that overlap.
For example, Corsa uses the same rule for checking calls in both TypeScript and JavaScript; Strada lets you skip parameters with type any.
And because Corsa uses the same rule for optional parameters, it fixes subtle Strada bugs with "strict": true in JavaScript.
We primarily want to support people writing modern JavaScript, using things like ES modules, classes, destructuring, etc. Not CommonJS modules and constructor functions, although those do still work. However, we have trimmed a lot of unused or underused features. This makes the implementation much simpler and more like TypeScript.
The biggest single removed area is support for Closure header files--any Closure-specific features, in fact. The tables below list removed Closure features along with the other removed features.
Reminder: JavaScript support in TypeScript falls into three main categories:
An expando declaration is when you declare a property just by assigning to it, on a function, class or empty object literal:
function f() {}
f.called = false;
| Name | Example | Substitute | Note |
|---|---|---|---|
| UnknownType | ? | any | |
| NamepathType | Module:file~id | import("file").id | TS has never had semantics for this |
@class | <pre><code>/** @class */</code> | ||
| <code>function C() {</code> |
<code>this.p = 1</code>
<code>}</code></pre> | Infer from <code>this.p=</code> or <code>C.prototype.m=</code> | Only inference from <code>this.p=</code> or <code>C.prototype.m=</code> is supported. |
| @throws | /** @throws {E} */ | Keep the same | TS never had semantics for this |
| @enum | <pre><code>/** @enum {number} /</code>
<code>const E = { A: 1, B: 2 }</code></pre> | <pre><code>/* @typedef {number} E */</code>
<code>/** @type {Record<string, E>} /</code>
<code>const E = { A: 1, B: 2 }</code></pre> | Closure feature. |
| @author | /** @author Finn <[email protected]> */ | Keep the same | @treehouse parses as a new tag in Corsa. |
| Postfix optional type | T? | T \| undefined | This was legacy in Closure |
| Postfix definite type | T! | T | This was legacy in Closure |
| Uppercase synonyms | String, Void, array | string, void, Array | |
| JSDoc index signatures | Object.<K,V> | Record<K, V> | |
| Identifier-named typedefs | /** @typedef {T} */ typeName; | /** @typedef {T} typeName */ | Closure feature. |
| Closure function syntax | function(string): void | (s: string) => void | |
| Automatic typeof insertion | <pre><code>const o = { a: 1 }</code>
<code>/* @type {o} /</code>
<code>var o2 = { a: 1 }</code></pre> | <pre><code>const o = { a: 1 }</code>
<code>/* @type {typeof o} */</code>
<code>var o2 = { a: 1 }</code></pre> | |
| @typedef nested names | /** @typedef {1} NS.T */ | Translate to .d.ts | Also applies to @callback |
| Name | Example | Substitute | Note |
|---|---|---|---|
| Fallback initialisers | f.x = f.x || init | if (!f.x) f.x = init | |
| Nested, undeclared expandos | <pre><code>var N = {};</code> | ||
| <code>N.X.Y = {}</code></pre> | <pre><code>var N = {};</code> | ||
| <code>N.X = {};</code> | |||
| <code>N.X.Y = {}</code></pre> | All intermediate expandos have to be assigned. Closure feature. | ||
| Constructor function whole-prototype assignment | <pre><code>C.prototype = {</code> | ||
| <code>m: function() { }</code> | |||
| <code>n: function() { }</code> | |||
| <code>}</code></pre> | <pre><code>C.prototype.m = function() { }</code> | ||
| <code>C.prototype.n = function() { }</code></pre> | Constructor function feature. See note at end. | ||
| Identifier declarations | <pre><code>class C {</code> | ||
| <code>constructor() {</code> |
<code>/\** @type {T} */</code>
<code>identifier;</code>
<code>}</code>
<code>}</code></pre> | <pre><code>class C {</code>
<code>/** @type {T} */</code>
<code>identifier;</code>
<code>constructor() { }</code>
<code>}</code></pre> | Closure feature. |
| this aliases | <pre><code>function C() {</code>
<code>var that = this</code>
<code>that.x = 12</code>
<code>}</code></pre> | <pre><code>function C() {</code>
<code>this.x = 12</code>
<code>}</code></pre> | even better:
<code>class C { this.x = 12 }</code> | |
| this alias for globalThis | this.globby = true | globalThis.globby = true | When used at the top level of a script |
| Name | Example | Substitute | Note |
|---|---|---|---|
| Nested, undeclared exports | exports.N.X.p = 1 | <pre><code>exports.N = {}</code> | |
| <code>exports.N.X = {}</code> | |||
| <code>exports.N.X.p = 1</code></pre> | Same as expando rules. | ||
| Ignored empty module.exports assignment | module.exports = {} | Delete this line | People used to write in this in case module.exports was not defined. |
this alias for module.exports | this.p = 1 | exports.p = 1 | When used at the top level of a CommonJS module. |
| Multiple assignments narrow with control flow | <pre><code>if (isWindows) {</code> | ||
| <code> exports.platform = 'win32'</code> | |||
| <code>} else {</code> | |||
| <code>exports.platform = 'posix'</code> | |||
| <code>}</code></pre> | Keep the same in most cases | This now unions instead; most uses have the same type in both branches. | |
Single-property access require | var readFile = require('fs').readFile | var { readFile } = require('fs') | |
Aliasing of module.exports | <pre><code>var mod = module.exports</code> | ||
| <code>mod.x = 1</code></pre> | module.exports.x = 1 |
...T? at the end of a tuple now fails with a parse error instead of a grammar error.import x as "OOPS" from "y") now contain the string's text instead of an empty identifier.string is no longer part of the type of comment./**.JSDoc types are parsed in normal type annotation position but show a grammar error. Corsa no longer parses the JSDoc types below, giving a parse error instead of a grammar error.
T? and T! types. Prefix ?T and !T are still parsed and !T continues to have no semantics.function(string,string): void types.? type.module:folder/file.CCorsa no longer parses the following JSDoc tags with a specific node type. They now parse as generic JSDocTag nodes.
@class/@constructor@throws@author@enum"strict": false, Corsa no longer allows omitting arguments for parameters with type undefined, unknown, or any:/** @param {unknown} x */
function f(x) {
return x;
}
f(); // Previously allowed, now an error
void can still be omitted, regardless of strict mode:
/** @param {void} x */
function f(x) {
return x;
}
f(); // Still allowed
Inferred type arguments may change. For example:
/** @type {any} */
var x = { a: 1, b: 2 };
var entries = Object.entries(x);
In Strada, entries: Array<[string, any]>. In Corsa it has type Array<[string, unknown]>, the same as in TypeScript.
/** @typedef {FORWARD | BACKWARD} Direction */
const FORWARD = 1,
BACKWARD = 2;
Must now use typeof the same way TS does:
/** @typedef {typeof FORWARD | typeof BACKWARD} Direction */
const FORWARD = 1,
BACKWARD = 2;
/** @param {...number} ns */
function sum(...ns) {}
is equivalent to
/** @param {number[]} ns */
function sum(...ns) {}
They have no other semantics.
/** @param {...number} ns */
function sum(ns) {}
Must now be written as
/** @param {...number} ns */
function sum(...ns) {}
= type no longer adds undefined even when strictNullChecks is offThis is a bug in Strada: it adds undefined to the type even when strictNullChecks is off.
This bug is fixed in Corsa.
/** @param {number=} x */
function f(x) {
return x;
}
will now have x?: number not x?: number | undefined with strictNullChecks off.
Regardless of strictness, it still makes parameters optional when used in a @param tag.
asserts annotation for an arrow function must be on the declaring variable, not on the arrow itself. This no longer works:/**
* @param {A} a
* @returns { asserts a is B }
*/
const foo = (a) => {
if (/** @type { B } */ (a).y !== 0) throw TypeError();
return undefined;
};
And must be written like this:
/**
* @type {(a: A) => asserts a is B}
*/
const foo = (a) => {
if (/** @type { B } */ (a).y !== 0) throw TypeError();
return undefined;
};
This is identical to the TypeScript rule.
@typedef and @callback in a class body are hoisted outside the class.This means the declarations are accessible outside the class and may conflict with similarly named declarations in the outer scope.
@class or @constructor does not make a function into a constructor function.Corsa ignores @class and @constructor. This makes a difference on a function without this-property assignments or associated prototype-function assignments.
@param tags now apply to at most one function.If they're in a place where they could apply to multiple functions, they apply only to the first one.
If you have "strict": true, you will see a noImplicitAny error on the now-untyped parameters.
/** @param {number} x */
var f = (x) => x,
g = (x) => x;
/** @param {number} [x] */
function f(x) {
return x;
}
This behaves the same as TypeScript's x?: number syntax.
Strada makes the parameter optional but does not add undefined to the type.
@type tags now prevent narrowing of the type./** @param {C | undefined} cu */
function f(cu) {
if (/** @type {any} */ (cu).undeclaredProperty) {
cu; // still has type C | undefined
}
}
In Strada, cu incorrectly narrows to C inside the if block, unlike with TS assertion syntax.
In Corsa, the behaviour is the same between TS and JS.
void 0 are no longer ignored as a special case:var o = {};
o.y = void 0;
creates a property y: undefined on o (which will widen to y: any if strictNullChecks is off).
class SharedClass {
constructor() {
/** @type {SharedId} */
this.id;
}
}
Provide an initializer or use a property declaration in the class body:
class SharedClass1 {
/** @type {SharedId} */
id;
}
class SharedClass2 {
constructor() {
/** @type {SharedId} */
this.id = 1;
}
}
prototype property of a function no longer makes it a constructor function:function Foo() {}
Foo.prototype = {
/** @param {number} x */
bar(x) {
return x;
},
};
If you still need to use constructor functions instead of classes, you should declare methods individually on the prototype:
function Foo() {}
/** @param {number} x */
Foo.prototype.bar = function (x) {
return x;
};
Although classes are a much better way to write this code.
undefined:To accommodate the pattern of initializing CommonJS exports to undefined (sometimes written as void 0) and then subsequently assigning their intended values, when CommonJS exports have multiple assignments and an initial assignment of undefined, the undefined is ignored when determining the type of the export.
exports.foo = exports.bar = void 0;
// Later in the same file...
exports.foo = 123 // Exported type is `123`
exports.bar = "abc" // Exported type is `"abc"`