Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: refreshed docs and onboarded eslint-doc-generator #67

Merged
merged 4 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslint-doc-generatorrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('eslint-doc-generator').GenerateOptions} */
module.exports = {
ruleDocTitleFormat: 'prefix-name',
};
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage
dist
27 changes: 19 additions & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
{
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"env": {
"node": true
},
"extends": ["eslint:recommended", "prettier"],
"overrides": [
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
{
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"files": ["**/*.{ts,tsx}"],
"parserOptions": {
"project": ["./tsconfig.json", "./tests/tsconfig.json"]
},
"rules": {
"@typescript-eslint/no-non-null-assertion": "off"
}
}
],
"plugins": ["@typescript-eslint", "@typescript-eslint/eslint-plugin", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"root": true,
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off"
"no-case-declarations": "off",
"no-constant-condition": "off"
}
}
4 changes: 4 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"no-inline-html": false,
"line-length": false
}
1 change: 1 addition & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
142 changes: 34 additions & 108 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# eslint-plugin-expect-type

ESLint plugin with $ExpectType and $ExpectError type assertions
> ESLint plugin with `^?` Twoslash, `$ExpectError`, and `$ExpectType` type assertions. ✨
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be in a separate PR, but it would be helpful to include some background on why you might want to do type assertions (testing types) and specifically why you might want to do them in this way (string matching avoids vagaries of assignability and lets you test the display of types).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah a good idea. Linking here to #50 👍


```ts
9001;
// ^? number

// $ExpectError
const value: string = 9001;

// $ExpectType number
9001;
```

## Installation

Expand All @@ -10,129 +21,44 @@ Make sure you have TypeScript and @typescript-eslint/parser installed, then inst
npm i -D eslint-plugin-expect-type
```

Please also make sure the following packages are also installed:

```sh
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
```
> See [typescript-eslint's Getting Started docs](https://typescript-eslint.io/docs) for how to run ESLint on TypeScript files.

## Usage

Please add the following options to your `.eslintrc`

```json
{
"extends": ["plugin:eslint-plugin-expect-type/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["eslint-plugin-expect-type"],
"root": true
}
```

Rule severity could be configured as follows
Add the following options to your [ESLint configuration file](https://eslint.org/docs/latest/user-guide/configuring/configuration-files):

```json
{
"rules": {
"expect-type/expect": "error"
}
"extends": ["plugin:expect-type/recommended"],
"plugins": ["eslint-plugin-expect-type"]
}
```

To skip Snapshot update when eslint is run with `--fix` could be configured as follows:

```json
{
"rules": {
"expect-type/expect": ["error", { "disableExpectTypeSnapshotFix": true }]
}
}
```

**Note: Make sure to use `eslint --ext .js,.ts` since by [default](https://eslint.org/docs/user-guide/command-line-interface#--ext) `eslint` will only search for .js files.**

## Adding $ExpectType and $ExpectError type assertions
danvk marked this conversation as resolved.
Show resolved Hide resolved

> A test file should be a piece of sample code that tests using the library. Tests are type-checked, but not run. To assert that an expression is of a given type, use $ExpectType. To assert that an expression causes a compile error, use $ExpectError. (Assertions will be checked by the expect lint rule.)

(https://github.com/Microsoft/dtslint#write-tests)

```ts
import foo from 'lib-to-test'; // foo is (n: number) => void

// $ExpectType void
foo(1);

// Can also write the assertion on the same line.
foo(2); // $ExpectType void

// $ExpectError
foo('bar');
```

## Adding \$ExpectTypeSnapshot

Uses snapshot saved in file as expected type for expression.

Example:

**foo.test.ts**

```ts
// $ExpectTypeSnapshot MyFooSnapshot
const Foo = {
a: 1,
n: 17,
} as const;
```

By running `eslint --fix` the following file will be created in the folder of `foo.test.ts`:
Then, you'll be able to use `^?`, `$ExpectError`, `$ExpectType`, and `$ExpectTypeSnapshot` comments in code assert on types.

```plaintext
__type-snapshots__/foo.test.ts.snap.json
```

By running `eslint` snapshot type will be matched with actual type and Error will be emitted in case types don't match.
<!-- prettier-ignore-start -->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks! I'll probably leave this as is for a bit, since it's less config for now. But this is good to know!

<!-- begin auto-generated rules list -->

### To create/update snapshots
💼 Configurations enabled in.\
✅ Set in the `recommended` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💭 Requires type information.

```sh
eslint --fix
```
| Name | Description | 💼 | 🔧 | 💭 |
| :----------------------------- | :----------------------------------------- | :- | :- | :- |
| [expect](docs/rules/expect.md) | Expects type error, type snapshot or type. | ✅ | 🔧 | 💭 |

## Twoslash syntax (^?)

This plugin also supports twoslash annotations, which is a comment line that starts with two slashes (`// `) and the `^?` identifier to annotate the symbol you're interested in:

```ts
const square = (x: number) => x * x;
const four = square(2);
// ^? const four: number
```

Multiline type annotations are also supported:

```ts
const vector = {
x: 3,
y: 4,
};
vector;
// ^? const vector: {
// x: number;
// y: number;
// }
```
<!-- end auto-generated rules list -->
<!-- prettier-ignore-end -->

## References

1. https://github.com/gcanti/dtslint
2. https://github.com/Microsoft/dtslint
3. https://github.com/SamVerschueren/tsd
You might consider using other popular type assertion libraries in the TypeScript ecosystem:

- **[expect-type](https://github.com/mmkal/expect-type)**: Provides functions that return assorted generic type assertion methods, such as `expectTypeOf('abc').toMatchTypeOf<string>()`.
- **[ts-expect](https://github.com/mmkal/expect-type)**: Provides generic type assertion function, used like `expectType<string>('abc')()`.
- **[tsd](https://github.com/SamVerschueren/tsd)**: Allows writing tests specifically for `.d.ts` definition files.

## Appreciation

Many thanks to @ibezkrovnyi for creating the initial version and core infrastructure of this package! 💖
Many thanks to [@ibezkrovnyi](https://github.com/ibezkrovnyi) for creating the initial version and core infrastructure of this package! 💖
146 changes: 146 additions & 0 deletions docs/rules/expect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# expect-type/expect

💼 This rule is enabled in the ✅ `recommended` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

💭 This rule requires type information.

<!-- end auto-generated rule header -->

Enforces that types indicated in special comments match the types of code values.

## Comment Types

The following kinds of comments are supported:

<!-- Markdownlint doesn't seem to understand the heading IDs... -->
<!-- markdownlint-disable link-fragments -->

- [`^?` (Twoslash Syntax)](#twoslash-syntax)
- [`$ExpectError`](#expecterror)
- [`$ExpectType`](#expecttype)
- [`$ExpectTypeSnapshot`](#expecttypesnapshot)

<!-- markdownlint-enable link-fragments -->

### Twoslash Syntax (`^?`)

Twoslash annotations are comment lines that start with two slashes (// ) and the ^? identifier to annotate a type below a value in code.
For example:

```ts
declare const getTextLength: (text: string) => number;

getTextLength('abc');
// ^? number

getTextLength;
// ^? (text: string) => number
```

Mismatching the type will cause a lint report:

```ts
'abc';
// ^? number
```

```plaintext
Expected type to be: number, got: string
```

Multiline type annotations are also supported:

```ts
const vector = {
x: 3,
y: 4,
};
vector;
// ^? const vector: {
// x: number;
// y: number;
// }
```

### `$ExpectError`

Place this above a line of code to assert that it causes a TypeScript type error.
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved

For example:

```ts
// $ExpectError
const value: number = 'abc';
```

> ⚠️ `$ExpectError` does not suppress TypeScript type errors.
> Only TypeScript comment directives such as `// @ts-expect-error` may do that.
> See [#65](https://github.com/JoshuaKGoldberg/eslint-plugin-expect-type/issues/65).

### `$ExpectType`

A comment containing `// $ExpectType` and a type, placed above or on a line of code.
It asserts that the value of the line of code is of that type.

For example:

```ts
declare const getTextLength: (text: string) => number;

// $ExpectType number
getTextLength('abc');

// $ExpectType (text: string) => number
getTextLength;
```

Mismatching the type will cause a lint report:

```ts
// $ExpectType number
'abc';
```

```plaintext
Expected type to be: number, got: string
```

### `$ExpectTypeSnapshot`

Similar to `$ExpectType`, but instead of a type, takes in a unique ID for a snapshot.
The snapshot will then be saved in a `__type-snapshots__/*.snap.json` file alongside the original `*`-named source file.

For example, given a `file.ts`:

```ts
declare const getTextLength: (text: string) => number;

// $ExpectTypeSnapshot FunctionCallExpression
getTextLength('abc');

// $ExpectTypeSnapshot FunctionIdentifier
getTextLength;
```

...a `__type-snapshots__/file.ts.snap.json` file will be created containing:

```json
{
"FunctionCallExpression": "number",
"FunctionIdentifier": "(text: string) => number"
}
```

These snapshots will automatically update whenever `eslint --fix` is run.

> ⛔️ [#14](https://github.com/JoshuaKGoldberg/eslint-plugin-expect-type/issues/14): There is currently a bug where the snapshots will _always_ update unless the later `disableExpectTypeSnapshotFix` option is set.
> Help wanted!

## Options

### `disableExpectTypeSnapshotFix`

Whether to disable `$ExpectTypeSnapshot` auto-fixing.
Defaults to `false`.
Loading