CHANGES.md lists intentional changes between the Strada (TypeScript) and Corsa (Go) compilers.
JavaScript support
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:
- JSDoc Tags
- Expando declarations
- CommonJS syntax
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;
JSDoc Tags and Types
| Name |
Example |
Substitute |
Note |
| UnknownType |
? |
any |
|
| NamepathType |
Module:file~id |
import("file").id |
TS has never had semantics for this |
@class |
/* @class /
function C() { this.p = 1
}
|
Infer from this.p= or C.prototype.m= |
Only inference from this.p= or C.prototype.m= is supported. |
@throws |
/** @throws {E} */ |
Keep the same |
TS never had semantics for this |
@enum |
/* @enum {number} /
const E = { A: 1, B: 2 }
|
/ @typedef {number} E */
/ @type {Record<string, E>} */
const E = { A: 1, B: 2 }
|
Closure feature. |
@author |
/** @author Finn <finn@treehouse.com> */ |
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 |
const o = { a: 1 }
/* @type {o} /
var o2 = { a: 1 }
|
const o = { a: 1 }
/* @type {typeof o} /
var o2 = { a: 1 }
|
|
@typedef nested names |
/** @typedef {1} NS.T */ |
Translate to .d.ts |
Also applies to @callback |
Expando declarations
| Name |
Example |
Substitute |
Note |
| Fallback initialisers |
`f.x = f.x \ |
init` |
if (!f.x) f.x = init |
|
| Nested, undeclared expandos |
var N = {};
N.X.Y = {}
|
var N = {};
N.X = {};
N.X.Y = {}
|
All intermediate expandos have to be assigned. Closure feature. |
| Constructor function whole-prototype assignment |
C.prototype = { m: function() { } n: function() { }
}
|
C.prototype.m = function() { }
C.prototype.n = function() { }
|
Constructor function feature. See note at end. |
| Identifier declarations |
class C { constructor() { /* @type {T} / identifier; }
}
|
class C { /* @type {T} / identifier; constructor() { }
}
|
Closure feature. |
this aliases |
function C() { var that = this that.x = 12
}
|
function C() { this.x = 12
}
|
even better: class C { this.x = 12 } |
|
this alias for globalThis |
this.globby = true |
globalThis.globby = true |
When used at the top level of a script |
CommonJS syntax
| Name |
Example |
Substitute |
Note |
| Nested, undeclared exports |
exports.N.X.p = 1 |
exports.N = {}
exports.N.X = {}
exports.N.X.p = 1
|
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 |
if (isWindows) {
exports.platform = 'win32'
} else { exports.platform = 'posix'
}
|
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 |
var mod = module.exports
mod.x = 1
|
module.exports.x = 1 |
Features yet to be implemented
Object.defineProperty for CommonJS exports and expandos. The compiler treats this as an alternate to the usual assignment syntax:
function f() {}
Object.defineProperty(f, "p", { value: 1, writable: true });
Component-Level Changes
Scanner
- Node positions use UTF8 offsets from the beginning of the file, not UTF16 offsets. Node positions in files with non-ASCII characters will be greater than before.
Parser
- Malformed
...T? at the end of a tuple now fails with a parse error instead of a grammar error.
- Malformed string ImportSpecifiers (
import x as "OOPS" from "y") now contain the string's text instead of an empty identifier.
- Empty binding elements no longer have a separate kind for OmittedExpression. Instead they have Kind=BindingElement with a nil Initialiser, Name and DotDotDotToken.
- ShorthandPropertyAssignment no longer includes an EqualsToken as a child when it has an ObjectAssignmentInitializer.
- JSDoc nodes now include leading whitespace in their location.
- The parser always parses a JSDocText node for comments in JSDoc.
string is no longer part of the type of comment.
- In cases where Strada did produce a JSDocText node, Corsa no longer (incorrectly) includes all leading and trailing whitespace/asterisks, as well as initial
/**.
- JSDocMemberName is now parsed as QualifiedName. These two nodes previously only differed by type, and now QualifiedName has a much less restrictive type for its left child.
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.
- No postfix
T? and T! types. Prefix ?T and !T are still parsed and !T continues to have no semantics.
- No Closure
function(string,string): void types.
- No JSDoc standalone
? type.
- No JSDoc module namepaths:
module:folder/file.C
Corsa no longer parses the following JSDoc tags with a specific node type. They now parse as generic JSDocTag nodes.
@class/@constructor
@throws
@author
@enum
Checker
Miscellaneous
When "strict": false, Corsa no longer allows omitting arguments for parameters with type undefined, unknown, or any:
function f(x) {
return x;
}
f();
void can still be omitted, regardless of strict mode:
function f(x) {
return x;
}
f();
Strada's JS-specific rules for inferring type arguments no longer apply in Corsa.
Inferred type arguments may change. For example:
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.
Values are no longer resolved as types in JSDoc type positions.
const FORWARD = 1,
BACKWARD = 2;
Must now use typeof the same way TS does:
const FORWARD = 1,
BACKWARD = 2;
JSDoc Types
JSDoc variadic types are now only synonyms for array types.
function sum(...ns) {}
is equivalent to
function sum(...ns) {}
They have no other semantics.
A variadic type on a parameter no longer makes it a rest parameter. The parameter must use standard rest syntax.
function sum(ns) {}
Must now be written as
function sum(...ns) {}
The postfix = type no longer adds undefined even when strictNullChecks is off
This is a bug in Strada: it adds undefined to the type even when strictNullChecks is off.
This bug is fixed in Corsa.
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.
JSDoc Tags
asserts annotation for an arrow function must be on the declaring variable, not on the arrow itself. This no longer works:
const foo = (a) => {
if ( (a).y !== 0) throw TypeError();
return undefined;
};
And must be written like this:
const foo = (a) => {
if ( (a).y !== 0) throw TypeError();
return undefined;
};
This is identical to the TypeScript rule.
Error messages on async functions that incorrectly return non-Promises now use the same error as TS.
@typedef and @callback in a class body are no longer accessible outside the class.
They must be moved outside the class to use them outside the class.
@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.
var f = (x) => x,
g = (x) => x;
Optional marking on parameter names now makes the parameter both optional and undefined:
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 assertions with @type tags now prevent narrowing of the type.
function f(cu) {
if ( (cu).undeclaredProperty) {
cu;
}
}
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.
Expandos
Expando assignments of 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).
A this-property expression with a type annotation in the constructor no longer creates a property:
class SharedClass {
constructor() {
this.id;
}
}
Provide an initializer or use a property declaration in the class body:
class SharedClass1 {
id;
}
class SharedClass2 {
constructor() {
this.id = 1;
}
}
Assigning an object literal to the prototype property of a function no longer makes it a constructor function:
function Foo() {}
Foo.prototype = {
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() {}
Foo.prototype.bar = function (x) {
return x;
};
Although classes are a much better way to write this code.
CommonJS
Chained exports no longer work:
exports.x = exports.y = 12;
Now only exports x, not y as well.
Exporting void 0 is no longer ignored as a special case:
exports.x = void 0;
exports.x = theRealExport;
This exports x: undefined not x: typeof theRealExport.
Property access on require no longer imports a single property from a module:
const x = require("y").x;
If you can't configure your package to use ESM syntax, you can use destructuring instead:
const { x } = require("y");