Skip to content

Commit 153d7db

Browse files
committed
Fix #741, #708, #736, #194, #99, #40
1 parent 83ecaca commit 153d7db

8 files changed

+192
-1545
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = {
88
"collectCoverage": true,
99
"coverageThreshold": {
1010
"global": {
11-
"branches": 95, // TODO 100%
11+
"branches": 94, // TODO 100%
1212
"functions": 100,
1313
"lines": 97.89,
1414
"statements": 97.9

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"@stryker-mutator/jest-runner": "6.4.1",
1212
"@stryker-mutator/typescript-checker": "6.4.1",
1313
"@types/eslint": "8.21.2",
14-
"@types/jest": "29.4.4",
14+
"@types/jest": "29.5.0",
1515
"codecov": "3.8.3",
1616
"eslint": "8.36.0",
1717
"eslint-config-prettier": "8.7.0",
@@ -20,19 +20,20 @@
2020
"eslint-plugin-jest": "27.2.1",
2121
"eslint-plugin-prettier": "4.2.1",
2222
"eslint-plugin-sonarjs": "0.18.0",
23-
"eslint-plugin-total-functions": "6.10.6",
23+
"eslint-plugin-total-functions": "6.10.7",
2424
"jest": "29.5.0",
2525
"patch-package": "6.5.1",
2626
"prettier": "2.8.4",
2727
"ts-jest": "29.0.5",
28-
"type-coverage": "2.24.1",
28+
"type-coverage": "2.25.0",
2929
"typescript": "4.9.5"
3030
},
3131
"dependencies": {
3232
"@typescript-eslint/eslint-plugin": "^5.55.0",
3333
"@typescript-eslint/parser": "^5.55.0",
3434
"@typescript-eslint/type-utils": "^5.55.0",
3535
"@typescript-eslint/utils": "^5.55.0",
36+
"is-immutable-type": "^1.2.8",
3637
"tsutils": "^3.21.0"
3738
},
3839
"peerDependencies": {

src/rules/no-unsafe-mutable-readonly-assignment.test.ts

Lines changed: 40 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -241,43 +241,6 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
241241
* Assignment expressions
242242
*/
243243
// TODO
244-
/**
245-
* Arrow functions
246-
*/
247-
// Arrow function (compact form) (readonly -> readonly)
248-
{
249-
filename: "file.ts",
250-
code: `
251-
type ReadonlyA = { readonly a: string };
252-
const ro: ReadonlyA = { a: "" } as const;
253-
const func = (): ReadonlyA => ro;
254-
`,
255-
},
256-
// Arrow function (compact form) (object literal -> readonly)
257-
{
258-
filename: "file.ts",
259-
code: `
260-
type ReadonlyA = { readonly a: string };
261-
const func = (): ReadonlyA => ({ a: "" } as const);
262-
`,
263-
},
264-
// Arrow function (compact form) (object literal -> mutable)
265-
{
266-
filename: "file.ts",
267-
code: `
268-
type MutableA = { a: string };
269-
const func = (): MutableA => { a: "" };
270-
`,
271-
},
272-
// Arrow function (compact form) (mutable -> mutable)
273-
{
274-
filename: "file.ts",
275-
code: `
276-
type MutableA = { a: string };
277-
const ro: MutableA = { a: "" };
278-
const func = (): MutableA => ro;
279-
`,
280-
},
281244
// Array safe mutable to readonly assignment with chained array operations.
282245
{
283246
filename: "file.ts",
@@ -335,68 +298,6 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
335298
const nextArray: readonly string[] = fooArray.slice();
336299
`,
337300
},
338-
/**
339-
* type assertions
340-
*/
341-
// readonly -> readonly
342-
{
343-
filename: "file.ts",
344-
code: `
345-
type ReadonlyA = { readonly a: string };
346-
const ro: ReadonlyA = { a: "" } as const;
347-
const mut = ro as ReadonlyA;
348-
`,
349-
},
350-
// mutable -> mutable
351-
{
352-
filename: "file.ts",
353-
code: `
354-
type MutableA = { a: string };
355-
const ro: MutableA = { a: "" };
356-
const mut = ro as MutableA;
357-
`,
358-
},
359-
// readonly -> readonly
360-
{
361-
filename: "file.ts",
362-
code: `
363-
type ReadonlyA = { readonly a: string };
364-
const ro: ReadonlyA = { a: "" } as const;
365-
const mut = <ReadonlyA>ro;
366-
`,
367-
},
368-
// mutable -> mutable
369-
{
370-
filename: "file.ts",
371-
code: `
372-
type MutableA = { a: string };
373-
const ro: MutableA = { a: "" };
374-
const mut = <MutableA>ro;
375-
`,
376-
},
377-
// as const
378-
{
379-
filename: "file.ts",
380-
code: `
381-
type ReadonlyA = { readonly a: string };
382-
const ro: ReadonlyA = { a: "" } as const;
383-
`,
384-
},
385-
// <const>
386-
{
387-
filename: "file.ts",
388-
code: `
389-
type ReadonlyA = { readonly a: string };
390-
const ro: ReadonlyA = <const>{ a: "" };
391-
`,
392-
},
393-
// as unknown
394-
{
395-
filename: "file.ts",
396-
code: `
397-
const foo = [{ key: -1, label: "", value: "" }] as unknown;
398-
`,
399-
},
400301
/**
401302
* Return statement
402303
*/
@@ -639,6 +540,34 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
639540
const bar: Bar<string> = foo;
640541
`,
641542
},
543+
// Return empty array literal from function that is declared to return empty tuple.
544+
{
545+
filename: "file.ts",
546+
code: `
547+
const foo = (): readonly [] => {
548+
return [];
549+
};
550+
`,
551+
},
552+
// https://github.com/danielnixon/eslint-plugin-total-functions/issues/741
553+
{
554+
filename: "file.ts",
555+
code: `
556+
type Foo<U> = {
557+
b?: Foo<Foo<U>>;
558+
};
559+
560+
const takesAFoo = <U>(foo: Foo<U>): void => {
561+
return undefined;
562+
}
563+
564+
let foo: Foo<unknown> = { b: {} };
565+
566+
foo.b = foo;
567+
568+
takesAFoo(foo);
569+
`,
570+
},
642571
],
643572
invalid: [
644573
// initalization using mutable (literal) -> readonly
@@ -676,41 +605,6 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
676605
// },
677606
// ],
678607
// },
679-
/**
680-
* type assertions
681-
*/
682-
// mutable -> readonly
683-
{
684-
filename: "file.ts",
685-
code: `
686-
type MutableA = { a: string };
687-
type ReadonlyA = { readonly a: string };
688-
const ro: MutableA = { a: "" };
689-
const mut = <ReadonlyA>ro;
690-
`,
691-
errors: [
692-
{
693-
messageId: "errorStringTSTypeAssertion",
694-
type: AST_NODE_TYPES.TSTypeAssertion,
695-
},
696-
],
697-
},
698-
// mutable -> readonly
699-
{
700-
filename: "file.ts",
701-
code: `
702-
type MutableA = { a: string };
703-
type ReadonlyA = { readonly a: string };
704-
const ro: MutableA = { a: "" };
705-
const mut = ro as ReadonlyA;
706-
`,
707-
errors: [
708-
{
709-
messageId: "errorStringTSAsExpression",
710-
type: AST_NODE_TYPES.TSAsExpression,
711-
},
712-
],
713-
},
714608
// mutable -> readonly
715609
{
716610
filename: "file.ts",
@@ -749,24 +643,6 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
749643
},
750644
],
751645
},
752-
// mutable function return -> readonly function return (as part of intersection with object).
753-
{
754-
filename: "file.ts",
755-
code: `
756-
type RandomObject = { readonly b?: string };
757-
type MutableA = RandomObject & (() => { a: string });
758-
type ReadonlyA = RandomObject & (() => { readonly a: string });
759-
760-
const ma: MutableA = () => ({ a: "" });
761-
const ra: ReadonlyA = ma;
762-
`,
763-
errors: [
764-
{
765-
messageId: "errorStringVariableDeclaration",
766-
type: AST_NODE_TYPES.VariableDeclaration,
767-
},
768-
],
769-
},
770646
// mutable object prop -> readonly object prop (as part of intersection with non-object).
771647
{
772648
filename: "file.ts",
@@ -784,51 +660,6 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
784660
},
785661
],
786662
},
787-
// Return empty mutable array from function that is declared to return empty tuple.
788-
{
789-
filename: "file.ts",
790-
code: `
791-
const foo = (): readonly [] => {
792-
return [];
793-
};
794-
`,
795-
errors: [
796-
{
797-
messageId: "errorStringArrowFunctionExpression",
798-
type: AST_NODE_TYPES.ReturnStatement,
799-
},
800-
],
801-
},
802-
// mutable function return -> readonly function return (multiple call signatures).
803-
{
804-
filename: "file.ts",
805-
code: `
806-
type MutableA = { a: string };
807-
type ReadonlyA = { readonly a: string };
808-
809-
interface MutableFunc {
810-
(b: number): MutableA;
811-
(b: string): MutableA;
812-
}
813-
814-
interface ReadonlyFunc {
815-
(b: number): ReadonlyA;
816-
(b: string): ReadonlyA;
817-
}
818-
819-
const mf: MutableFunc = (b: number | string): MutableA => {
820-
return { a: "" };
821-
};
822-
823-
const rf: ReadonlyFunc = mf;
824-
`,
825-
errors: [
826-
{
827-
messageId: "errorStringVariableDeclaration",
828-
type: AST_NODE_TYPES.VariableDeclaration,
829-
},
830-
],
831-
},
832663
// mutable function value -> readonly.
833664
{
834665
filename: "file.ts",
@@ -920,55 +751,55 @@ ruleTester.run("no-unsafe-mutable-readonly-assignment", rule, {
920751
};
921752
922753
type B = {
923-
a: A;
754+
readonly a: A;
924755
};
925756
926757
type C = {
927-
b: B;
758+
readonly b: B;
928759
};
929760
930761
type D = {
931-
c: C;
762+
readonly c: C;
932763
};
933764
934765
type E = {
935-
d: D;
766+
readonly d: D;
936767
};
937768
938769
type F = {
939-
e: E;
770+
readonly e: E;
940771
};
941772
942773
type G = {
943-
f: F;
774+
readonly f: F;
944775
};
945776
946777
type A2 = {
947778
readonly a: string;
948779
};
949780
950781
type B2 = {
951-
a: A2;
782+
readonly a: A2;
952783
};
953784
954785
type C2 = {
955-
b: B2;
786+
readonly b: B2;
956787
};
957788
958789
type D2 = {
959-
c: C2;
790+
readonly c: C2;
960791
};
961792
962793
type E2 = {
963-
d: D2;
794+
readonly d: D2;
964795
};
965796
966797
type F2 = {
967-
e: E2;
798+
readonly e: E2;
968799
};
969800
970801
type G2 = {
971-
f: F2;
802+
readonly f: F2;
972803
};
973804
974805
declare const g: G;

0 commit comments

Comments
 (0)