Skip to content

Commit e8824e1

Browse files
committed
Initial commit
0 parents  commit e8824e1

21 files changed

+2871
-0
lines changed

.eslintplugin.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.rules = {
2+
"invariant-mutable-properties" : require("./dist/invariant-mutable-properties"),
3+
};

.eslintrc-base.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"parser": "@typescript-eslint/parser",
3+
"plugins": ["@typescript-eslint", "local"],
4+
"parserOptions": {
5+
"ecmaVersion": 8,
6+
"sourceType": "module",
7+
"ecmaFeatures": {
8+
},
9+
10+
"tsconfigRootDir": "./src",
11+
"useJSXTextNode": true,
12+
"warnOnUnsupportedTypeScriptVersion": true
13+
},
14+
"rules": {
15+
"local/invariant-mutable-properties": [
16+
"error",
17+
{
18+
"reportTsSimpleTypeCrash": false
19+
}
20+
],
21+
22+
"func-call-spacing": "off",
23+
"@typescript-eslint/func-call-spacing": ["error"],
24+
"@typescript-eslint/no-floating-promises": ["error"],
25+
"@typescript-eslint/no-for-in-array": ["error"],
26+
"@typescript-eslint/no-misused-promises": [
27+
"off",
28+
{
29+
"checksVoidReturn": true,
30+
"checksConditionals": true
31+
}
32+
],
33+
"@typescript-eslint/no-non-null-assertion": ["error"],
34+
"@typescript-eslint/no-this-alias": [
35+
"error",
36+
{
37+
"allowDestructuring": false,
38+
"allowedNames": []
39+
}
40+
],
41+
"@typescript-eslint/prefer-for-of": ["error"],
42+
"no-return-await": ["error"],
43+
"require-await": "off",
44+
45+
"@typescript-eslint/require-await": "off",
46+
47+
"@typescript-eslint/strict-boolean-expressions": ["error"],
48+
"@typescript-eslint/type-annotation-spacing": [
49+
"off",
50+
{
51+
"before": true,
52+
"after": true
53+
}
54+
],
55+
"@typescript-eslint/prefer-string-starts-ends-with": ["error"],
56+
"@typescript-eslint/unbound-method": ["error"]
57+
}
58+
}

.eslintrc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//https://github.com/typescript-eslint/typescript-eslint/issues/101#issuecomment-499303058
2+
module.exports = {
3+
extends: "./.eslintrc-base.json",
4+
parserOptions: {
5+
project: "./tsconfig.json",
6+
tsconfigRootDir: __dirname,
7+
}
8+
};

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
### `invariant-mutable-properties`
2+
3+
A prototype of a [`typescript-eslint`](https://github.com/typescript-eslint/typescript-eslint) rule
4+
that treats mutable properties as invariant.
5+
6+
This rule uses [`ts-simple-type`](https://github.com/runem/ts-simple-type) for type checking
7+
because there is no public type checking API in TypeScript [yet](https://github.com/microsoft/TypeScript/issues/9879).
8+
9+
-----
10+
11+
### Configuration
12+
13+
```ts
14+
interface Config {
15+
//Defaults to `true`
16+
//If ts-simple-type crashes, file an issue with ts-simple-type repo.
17+
//At the moment, it will crash for complex types and some generic functions.
18+
//You might want to set this to `false` if ts-simple-type crashes too often.
19+
"reportTsSimpleTypeCrash": boolean,
20+
//Defaults to `true`
21+
//If the rule crashes, file an issue
22+
"reportRuleCrash": boolean
23+
}
24+
```
25+
26+
See [`.eslintrc-base.json`](.eslintrc-base.json) for more details
27+
28+
-----
29+
30+
### Testing
31+
32+
1. Clone this repo
33+
1. `npm install`
34+
1. `npm run test`
35+
36+
+ You may build with `npm run build`.
37+
+ You may lint with `npm run lint`
38+
39+
-----
40+
41+
### Examples
42+
43+
At the moment, TypeScript treats mutable properties as covariant,
44+
```ts
45+
const src : { x : number } = { x : 34 };
46+
/**
47+
* Assignment allowed, `x` is covariant
48+
*/
49+
const dst : { x : number|string } = src;
50+
51+
console.log(src.x); //34
52+
dst.x = "Oops!";
53+
console.log(src.x); //Oops!
54+
```
55+
[Playground](http://www.typescriptlang.org/play/#code/MYewdgzgLgBBBOwYC4YG8YA8UzAVwFsAjAU3hgF8YBedLHAZgBZKBuAKAHoAqb9mbjACCECAEsA5mAIkwsAIYAbRSADuJACYAaGAANMumGIgxQAN3nwx8uf26d2oSLA3QcGbKnzEyAH2hWYBKUNHCIHI7gECCKJAB0KhIAFAjAcZgAlKwwnJzM7K5Q6aEARADyIAAOEACEJRxO0bEJIMmp6Vk5nBXVNUA)
56+
57+
-----
58+
59+
As you can see from the above example, this is not safe.
60+
61+
This rule treats mutable properties as invariant,
62+
```ts
63+
const src : { x : number } = { x : 34 };
64+
/**
65+
* Lint Error: Mutable properties are invariant; x
66+
*/
67+
const dst : { x : number|string } = src;
68+
```
69+
70+
-----
71+
72+
`readonly` properties are still covariant,
73+
```ts
74+
const src : { x : number } = { x : 34 };
75+
/**
76+
* Assignment allowed, `readonly x` is covariant
77+
*/
78+
const dst : { readonly x : number|string } = src;
79+
```
80+
81+
-----
82+
83+
See [tests](test/src/rules/invariant-mutable-properties.ts) for more examples.
84+
85+
-----
86+
87+
### TODO
88+
89+
+ Proper support for generics (and tests!)
90+
+ Proper checking for object/array literals (and tests!)
91+
92+
Right now, it always assumes object/array literals are safe but this is not true.
93+
94+
Ctrl+F `@todo` for examples
95+
96+
+ More tests
97+
98+
+ Use more stable type-checking API
99+
100+
`ts-simple-type` crashes with generic functions and complex types often, at the moment.
101+
102+
-----
103+
104+
The rule does not handle generics properly yet,
105+
```ts
106+
function foo<SrcT extends { x : number }> (src : SrcT) {
107+
//Right now, the rule thinks this is safe
108+
//but this is not true
109+
const dst : { x : number } = src;
110+
//Imagine SrcT is { x : 2 }
111+
//Boom, src.x now has 1 instead of 2
112+
dst.x = 1;
113+
}
114+
const src : { x : 2 } = { x : 2 };
115+
console.log(src.x); //2
116+
foo<{ x : 2 }>(src);
117+
console.log(src.x); //1
118+
```
119+
[Playground](http://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABMOcA8BlAThAKogUwA8oCwATAZ0QG9EjEAuRMEAWwCMCtEBfAPkQAKSjiaJseAJS0AUIgWIA9EoBKMAOYALKCzgB3ADSIoWgoiwgANudMwwAa2p3qMapQCGwAvMUqOILouiG56QZY+iogQCJS6VLrMdAzMrJzcfIgAvIiiEADcvgoqAJJsHhr25pL4ocniAEx8RcpKAEKobMZ5AHQMYAaIWh7UAIwhYHEEHuSIcMCIDS0JfdmIo4W8sjGTunni9cxNvGuHi3yFO5RwNj1WcBoiOH1S+a1LKOhnx-xPEK-bWI3Ah3B5-F5vFSjWRAA)
120+
121+
-----
122+
123+
The rule does not handle object and array literals properly yet,
124+
```ts
125+
const src : {
126+
nested : {
127+
doNotMutateMePlease : string,
128+
}
129+
} = {
130+
nested : {
131+
doNotMutateMePlease : "please?"
132+
}
133+
};
134+
//Right now, the rule thinks object literals are **ALWAYS** safe
135+
//but this is not true
136+
const dst : {
137+
nested : {
138+
doNotMutateMePlease : string|number,
139+
}
140+
} = {
141+
...src,
142+
};
143+
//Boom.
144+
//src.nested.doNotMutateMePlease is now number and not string
145+
console.log(src.nested.doNotMutateMePlease); //"please?"
146+
dst.nested.doNotMutateMePlease = 34;
147+
console.log(src.nested.doNotMutateMePlease); //34
148+
```
149+
[Playground](http://www.typescriptlang.org/play/#code/MYewdgzgLgBBBOwYC4YG8BQNszAU2jwBMV0scKiQA5EKAWQFcoBDKPevABQBs8WIeUtHgBLMAHMANOWwBfDHJgBeMhXyESqTBUo06TVu069+g0gCIADnwF4A-BdkwFcgNwYA9J4BKoiQAWsGAgAO5SMFABQvCMfJEB4gDWEDAgAEYAVnjAsDyi7PAsPKks8EIAVBUAggAyAOrVAJoAylVwLABmeF6e6cwJoqlDuHSRsT2gkLBE0KQ6OBrsWmq62FS0DMxsHNy25qgi4hIAPmCMALbpePAyFK4qq9gAdK8IwDLuvQBCICAXz1672eS2Izw2Bm2xj2ZiEIxCoVwl2u8BgLDAJBCsCOkgwUwgID4zx4IAkAApgaCiOD9FsjLtTHYAJRuGDeaz7BxOWZQEEEZY0zaGHYmTmPADMABYPPjCXhiaSKYg+ZpBZD6aLYSy2Z4pUA)
150+
151+
152+
-----
153+
154+
I am not very familiar with TypeScript's API, or `typescript-eslint`'s API.
155+
156+
If someone out there is more familiar with them and
157+
agrees making mutable properties invariant would be pretty cool,
158+
please improve this rule and/or create a better version of it!

0 commit comments

Comments
 (0)