diff --git a/CHANGES.md b/CHANGES.md index 619b821b82..86394dfcc0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,84 @@ -CHANGES.md lists intentional changes between the Strada (Typescript) and Corsa (Go) compilers. +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: + +```js +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} */
const E = { A: 1, B: 2 }
| Closure feature. | +| `@author` | `/** @author Finn */` | 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.` | `Record` | | +| 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: + +```js +function f() {} +Object.defineProperty(f, "p", { value: 1, writable: true }); +``` + +# Component-Level Changes ## Scanner @@ -24,7 +104,277 @@ JSDoc types are parsed in normal type annotation position but show a grammar err Corsa no longer parses the following JSDoc tags with a specific node type. They now parse as generic JSDocTag nodes. -1. `@class` +1. `@class`/`@constructor` 2. `@throws` 3. `@author` 4. `@enum` + +## Checker + +### Miscellaneous + +#### When `"strict": false`, Corsa no longer allows omitting arguments for parameters with type `undefined`, `unknown`, or `any`: + +```js +/** @param {unknown} x */ +function f(x) { + return x; +} +f(); // Previously allowed, now an error +``` + +`void` can still be omitted, regardless of strict mode: + +```js +/** @param {void} x */ +function f(x) { + return x; +} +f(); // Still allowed +``` + +#### Strada's JS-specific rules for inferring type arguments no longer apply in Corsa. + +Inferred type arguments may change. For example: + +```js +/** @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. + +#### Values are no longer resolved as types in JSDoc type positions. + +```js +/** @typedef {FORWARD | BACKWARD} Direction */ +const FORWARD = 1, + BACKWARD = 2; +``` + +Must now use `typeof` the same way TS does: + +```js +/** @typedef {typeof FORWARD | typeof BACKWARD} Direction */ +const FORWARD = 1, + BACKWARD = 2; +``` + +### JSDoc Types + +#### JSDoc variadic types are now only synonyms for array types. + +```js +/** @param {...number} ns */ +function sum(...ns) {} +``` + +is equivalent to + +```js +/** @param {number[]} ns */ +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. + +```js +/** @param {...number} ns */ +function sum(ns) {} +``` + +Must now be written as + +```js +/** @param {...number} ns */ +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. + +```js +/** @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. + +### JSDoc Tags + +#### `asserts` annotation for an arrow function must be on the declaring variable, not on the arrow itself. This no longer works: + +```js +/** + * @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: + +```js +/** + * @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. + +#### 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. + +```js +/** @param {number} x */ +var f = (x) => x, + g = (x) => x; +``` + +#### Optional marking on parameter names now makes the parameter both optional and undefined: + +```js +/** @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 assertions with `@type` tags now prevent narrowing of the type. + +```js +/** @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. + +### Expandos + +#### Expando assignments of `void 0` are no longer ignored as a special case: + +```js +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: + +```js +class SharedClass { + constructor() { + /** @type {SharedId} */ + this.id; + } +} +``` + +Provide an initializer or use a property declaration in the class body: + +```js +class SharedClass1 { + /** @type {SharedId} */ + id; +} +class SharedClass2 { + constructor() { + /** @type {SharedId} */ + this.id = 1; + } +} +``` + +#### Assigning an object literal to the `prototype` property of a function no longer makes it a constructor function: + +```js +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: + +```js +function Foo() {} +/** @param {number} x */ +Foo.prototype.bar = function (x) { + return x; +}; +``` + +Although classes are a much better way to write this code. + +### CommonJS + +#### Chained exports no longer work: + +```js +exports.x = exports.y = 12; +``` + +Now only exports `x`, not `y` as well. + +#### Exporting `void 0` is no longer ignored as a special case: + +```js +exports.x = void 0; +// several lines later... +exports.x = theRealExport; +``` + +This exports `x: undefined` not `x: typeof theRealExport`. + +#### Property access on `require` no longer imports a single property from a module: + +```js +const x = require("y").x; +``` + +If you can't configure your package to use ESM syntax, you can use destructuring instead: + +```js +const { x } = require("y"); +``` diff --git a/testdata/baselines/reference/submodule/conformance/assertionTypePredicates2.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assertionTypePredicates2.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/assertionTypePredicates2.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/assertionTypePredicates2.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/assertionsAndNonReturningFunctions.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assertionsAndNonReturningFunctions.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/assertionsAndNonReturningFunctions.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/assertionsAndNonReturningFunctions.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/asyncArrowFunction_allowJs.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/asyncArrowFunction_allowJs.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/asyncArrowFunction_allowJs.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/asyncArrowFunction_allowJs.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/asyncArrowFunction_allowJs.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/asyncArrowFunction_allowJs.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/asyncArrowFunction_allowJs.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/asyncArrowFunction_allowJs.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/asyncFunctionDeclaration16_es5.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/asyncFunctionDeclaration16_es5.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/asyncFunctionDeclaration16_es5.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/asyncFunctionDeclaration16_es5.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/asyncFunctionDeclaration16_es5.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/asyncFunctionDeclaration16_es5.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/asyncFunctionDeclaration16_es5.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/asyncFunctionDeclaration16_es5.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/binderUninitializedModuleExportsAssignment.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/binderUninitializedModuleExportsAssignment.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/binderUninitializedModuleExportsAssignment.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/binderUninitializedModuleExportsAssignment.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callOfPropertylessConstructorFunction.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callOfPropertylessConstructorFunction.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callOfPropertylessConstructorFunction.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callOfPropertylessConstructorFunction.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/callOfPropertylessConstructorFunction.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callOfPropertylessConstructorFunction.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callOfPropertylessConstructorFunction.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callOfPropertylessConstructorFunction.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callWithMissingVoidUndefinedUnknownAnyInJs(strict=false).errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callWithMissingVoidUndefinedUnknownAnyInJs(strict=false).errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callWithMissingVoidUndefinedUnknownAnyInJs(strict=false).errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callWithMissingVoidUndefinedUnknownAnyInJs(strict=false).errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callbackOnConstructor.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callbackOnConstructor.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackTag2.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackTag2.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callbackTag2.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callbackTag2.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackTag2.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackTag2.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callbackTag2.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callbackTag2.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackTag4.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackTag4.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callbackTag4.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callbackTag4.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/callbackTagNamespace.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/callbackTagNamespace.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/callbackTagNamespace.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/callbackTagNamespace.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/checkExportsObjectAssignProperty.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/checkExportsObjectAssignProperty.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/checkExportsObjectAssignProperty.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/checkExportsObjectAssignProperty.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/checkExportsObjectAssignProperty.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/checkExportsObjectAssignProperty.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/checkExportsObjectAssignProperty.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/checkExportsObjectAssignProperty.types.diff diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt.diff diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.types.diff b/testdata/baselines/reference/submoduleAccepted/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.types.diff similarity index 100% rename from testdata/baselines/reference/submodule/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.types.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.types.diff diff --git a/testdata/submoduleAccepted.txt b/testdata/submoduleAccepted.txt index c25f9c2c77..d23a697106 100644 --- a/testdata/submoduleAccepted.txt +++ b/testdata/submoduleAccepted.txt @@ -1,6 +1,36 @@ # Diff files to instead write to submoduleAccepted as "accepted" changes. + +## jsdoc ## +conformance/assertionsAndNonReturningFunctions.types.diff +conformance/assertionTypePredicates2.errors.txt.diff +conformance/assignmentToVoidZero1.errors.txt.diff +conformance/assignmentToVoidZero1.types.diff +conformance/asyncArrowFunction_allowJs.errors.txt.diff +conformance/asyncArrowFunction_allowJs.types.diff +conformance/asyncFunctionDeclaration16_es5.errors.txt.diff +conformance/asyncFunctionDeclaration16_es5.types.diff +conformance/binderUninitializedModuleExportsAssignment.types.diff +conformance/callbackOnConstructor.errors.txt.diff +conformance/callbackOnConstructor.types.diff +conformance/callbackTag2.errors.txt.diff +conformance/callbackTag2.types.diff +conformance/callbackTag4.types.diff +conformance/callbackTagNamespace.types.diff +conformance/callbackTagVariadicType.types.diff +conformance/callOfPropertylessConstructorFunction.errors.txt.diff +conformance/callOfPropertylessConstructorFunction.types.diff +conformance/callWithMissingVoidUndefinedUnknownAnyInJs(strict=false).errors.txt.diff +conformance/checkExportsObjectAssignProperty.errors.txt.diff +conformance/checkExportsObjectAssignProperty.types.diff +conformance/checkJsdocOnEndOfFile.types.diff +conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt.diff +conformance/checkJsdocParamOnVariableDeclaredFunctionExpression.types.diff +conformance/checkParamTag1.types.diff conformance/node10Alternateresult_noTypes.errors.txt.diff conformance/node10AlternateResult_noResolution.errors.txt.diff +conformance/node10AlternateResult_noResolution.errors.txt.diff + +## inlay hints ## fourslash/inlayHints/inlayHintsInteractiveParameterNamesWithComments.baseline.diff fourslash/inlayHints/inlayHintsImportType2.baseline.diff fourslash/inlayHints/inlayHintsInteractiveImportType2.baseline.diff @@ -22,4 +52,4 @@ fourslash/inlayHints/inlayHintsReturnType.baseline.diff fourslash/inlayHints/inlayHintsThisParameter.baseline.diff fourslash/inlayHints/inlayHintsVariableTypes1.baseline.diff fourslash/inlayHints/inlayHintsVariableTypes2.baseline.diff -fourslash/inlayHints/inlayHintsWithClosures.baseline.diff \ No newline at end of file +fourslash/inlayHints/inlayHintsWithClosures.baseline.diff