Skip to content

Commit 3931b72

Browse files
author
Andy
authored
noUnusedLocals: Destructuring assignment is a write (#26365)
* noUnusedLocals: Destructuring assignment is a write * Code review * Clarify test
1 parent 530a530 commit 3931b72

File tree

6 files changed

+206
-15
lines changed

6 files changed

+206
-15
lines changed

src/compiler/utilities.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,13 @@ namespace ts {
23312331
return node;
23322332
}
23332333

2334+
function skipParenthesesUp(node: Node): Node {
2335+
while (node.kind === SyntaxKind.ParenthesizedExpression) {
2336+
node = node.parent;
2337+
}
2338+
return node;
2339+
}
2340+
23342341
// a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
23352342
export function isDeleteTarget(node: Node): boolean {
23362343
if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) {
@@ -4206,6 +4213,8 @@ namespace ts {
42064213
if (!parent) return AccessKind.Read;
42074214

42084215
switch (parent.kind) {
4216+
case SyntaxKind.ParenthesizedExpression:
4217+
return accessKind(parent);
42094218
case SyntaxKind.PostfixUnaryExpression:
42104219
case SyntaxKind.PrefixUnaryExpression:
42114220
const { operator } = parent as PrefixUnaryExpression | PostfixUnaryExpression;
@@ -4217,13 +4226,35 @@ namespace ts {
42174226
: AccessKind.Read;
42184227
case SyntaxKind.PropertyAccessExpression:
42194228
return (parent as PropertyAccessExpression).name !== node ? AccessKind.Read : accessKind(parent);
4229+
case SyntaxKind.PropertyAssignment: {
4230+
const parentAccess = accessKind(parent.parent);
4231+
// In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write.
4232+
return node === (parent as PropertyAssignment).name ? reverseAccessKind(parentAccess) : parentAccess;
4233+
}
4234+
case SyntaxKind.ShorthandPropertyAssignment:
4235+
// Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals.
4236+
return node === (parent as ShorthandPropertyAssignment).objectAssignmentInitializer ? AccessKind.Read : accessKind(parent.parent);
4237+
case SyntaxKind.ArrayLiteralExpression:
4238+
return accessKind(parent);
42204239
default:
42214240
return AccessKind.Read;
42224241
}
42234242

42244243
function writeOrReadWrite(): AccessKind {
42254244
// If grandparent is not an ExpressionStatement, this is used as an expression in addition to having a side effect.
4226-
return parent.parent && parent.parent.kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
4245+
return parent.parent && skipParenthesesUp(parent.parent).kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
4246+
}
4247+
}
4248+
function reverseAccessKind(a: AccessKind): AccessKind {
4249+
switch (a) {
4250+
case AccessKind.Read:
4251+
return AccessKind.Write;
4252+
case AccessKind.Write:
4253+
return AccessKind.Read;
4254+
case AccessKind.ReadWrite:
4255+
return AccessKind.ReadWrite;
4256+
default:
4257+
return Debug.assertNever(a);
42274258
}
42284259
}
42294260

tests/baselines/reference/noUnusedLocals_writeOnly.errors.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
tests/cases/compiler/noUnusedLocals_writeOnly.ts(1,12): error TS6133: 'x' is declared but its value is never read.
2-
tests/cases/compiler/noUnusedLocals_writeOnly.ts(10,9): error TS6133: 'z' is declared but its value is never read.
2+
tests/cases/compiler/noUnusedLocals_writeOnly.ts(18,9): error TS6133: 'z' is declared but its value is never read.
33

44

55
==== tests/cases/compiler/noUnusedLocals_writeOnly.ts (2 errors) ====
6-
function f(x = 0) {
6+
function f(x = 0, b = false) {
77
~
88
!!! error TS6133: 'x' is declared but its value is never read.
9+
// None of these statements read from 'x', so it will be marked unused.
910
x = 1;
1011
x++;
1112
x /= 2;
13+
([x] = [1]);
14+
({ x } = { x: 1 });
15+
({ x: x } = { x: 1 });
16+
({ a: [{ b: x }] } = { a: [{ b: 1 }] });
17+
({ x = 2 } = { x: b ? 1 : undefined });
18+
let used = 1;
19+
({ x = used } = { x: b ? 1 : undefined });
1220

1321
let y = 0;
1422
// This is a write access to y, but not a write-*only* access.
@@ -19,4 +27,5 @@ tests/cases/compiler/noUnusedLocals_writeOnly.ts(10,9): error TS6133: 'z' is dec
1927
!!! error TS6133: 'z' is declared but its value is never read.
2028
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
2129
}
30+
function f2(_: ReadonlyArray<number>): void {}
2231

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
//// [noUnusedLocals_writeOnly.ts]
2-
function f(x = 0) {
2+
function f(x = 0, b = false) {
3+
// None of these statements read from 'x', so it will be marked unused.
34
x = 1;
45
x++;
56
x /= 2;
7+
([x] = [1]);
8+
({ x } = { x: 1 });
9+
({ x: x } = { x: 1 });
10+
({ a: [{ b: x }] } = { a: [{ b: 1 }] });
11+
({ x = 2 } = { x: b ? 1 : undefined });
12+
let used = 1;
13+
({ x = used } = { x: b ? 1 : undefined });
614

715
let y = 0;
816
// This is a write access to y, but not a write-*only* access.
@@ -11,17 +19,30 @@ function f(x = 0) {
1119
let z = 0;
1220
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
1321
}
22+
function f2(_: ReadonlyArray<number>): void {}
1423

1524

1625
//// [noUnusedLocals_writeOnly.js]
17-
function f(x) {
26+
"use strict";
27+
function f(x, b) {
1828
if (x === void 0) { x = 0; }
29+
if (b === void 0) { b = false; }
30+
var _a, _b;
31+
// None of these statements read from 'x', so it will be marked unused.
1932
x = 1;
2033
x++;
2134
x /= 2;
35+
(x = [1][0]);
36+
(x = { x: 1 }.x);
37+
(x = { x: 1 }.x);
38+
(x = { a: [{ b: 1 }] }.a[0].b);
39+
(_a = { x: b ? 1 : undefined }.x, x = _a === void 0 ? 2 : _a);
40+
var used = 1;
41+
(_b = { x: b ? 1 : undefined }.x, x = _b === void 0 ? used : _b);
2242
var y = 0;
2343
// This is a write access to y, but not a write-*only* access.
2444
f(y++);
2545
var z = 0;
2646
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
2747
}
48+
function f2(_) { }
Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
=== tests/cases/compiler/noUnusedLocals_writeOnly.ts ===
2-
function f(x = 0) {
2+
function f(x = 0, b = false) {
33
>f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0))
44
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
5+
>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17))
56

7+
// None of these statements read from 'x', so it will be marked unused.
68
x = 1;
79
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
810

@@ -12,19 +14,58 @@ function f(x = 0) {
1214
x /= 2;
1315
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
1416

17+
([x] = [1]);
18+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
19+
20+
({ x } = { x: 1 });
21+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 6, 6))
22+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 6, 14))
23+
24+
({ x: x } = { x: 1 });
25+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 7, 6))
26+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
27+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 7, 17))
28+
29+
({ a: [{ b: x }] } = { a: [{ b: 1 }] });
30+
>a : Symbol(a, Decl(noUnusedLocals_writeOnly.ts, 8, 6))
31+
>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 8, 12))
32+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11))
33+
>a : Symbol(a, Decl(noUnusedLocals_writeOnly.ts, 8, 26))
34+
>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 8, 32))
35+
36+
({ x = 2 } = { x: b ? 1 : undefined });
37+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 9, 6))
38+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 9, 18))
39+
>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17))
40+
>undefined : Symbol(undefined)
41+
42+
let used = 1;
43+
>used : Symbol(used, Decl(noUnusedLocals_writeOnly.ts, 10, 7))
44+
45+
({ x = used } = { x: b ? 1 : undefined });
46+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 11, 6))
47+
>used : Symbol(used, Decl(noUnusedLocals_writeOnly.ts, 10, 7))
48+
>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 11, 21))
49+
>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17))
50+
>undefined : Symbol(undefined)
51+
1552
let y = 0;
16-
>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 5, 7))
53+
>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 13, 7))
1754

1855
// This is a write access to y, but not a write-*only* access.
1956
f(y++);
2057
>f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0))
21-
>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 5, 7))
58+
>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 13, 7))
2259

2360
let z = 0;
24-
>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 9, 7))
61+
>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 17, 7))
2562

2663
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
2764
>f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0))
28-
>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 9, 7))
65+
>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 17, 7))
2966
}
67+
function f2(_: ReadonlyArray<number>): void {}
68+
>f2 : Symbol(f2, Decl(noUnusedLocals_writeOnly.ts, 19, 1))
69+
>_ : Symbol(_, Decl(noUnusedLocals_writeOnly.ts, 20, 12))
70+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --))
3071

tests/baselines/reference/noUnusedLocals_writeOnly.types

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
=== tests/cases/compiler/noUnusedLocals_writeOnly.ts ===
2-
function f(x = 0) {
3-
>f : (x?: number) => void
2+
function f(x = 0, b = false) {
3+
>f : (x?: number, b?: boolean) => void
44
>x : number
55
>0 : 0
6+
>b : boolean
7+
>false : false
68

9+
// None of these statements read from 'x', so it will be marked unused.
710
x = 1;
811
>x = 1 : 1
912
>x : number
@@ -18,14 +21,87 @@ function f(x = 0) {
1821
>x : number
1922
>2 : 2
2023

24+
([x] = [1]);
25+
>([x] = [1]) : [number]
26+
>[x] = [1] : [number]
27+
>[x] : [number]
28+
>x : number
29+
>[1] : [number]
30+
>1 : 1
31+
32+
({ x } = { x: 1 });
33+
>({ x } = { x: 1 }) : { x: number; }
34+
>{ x } = { x: 1 } : { x: number; }
35+
>{ x } : { x: number; }
36+
>x : number
37+
>{ x: 1 } : { x: number; }
38+
>x : number
39+
>1 : 1
40+
41+
({ x: x } = { x: 1 });
42+
>({ x: x } = { x: 1 }) : { x: number; }
43+
>{ x: x } = { x: 1 } : { x: number; }
44+
>{ x: x } : { x: number; }
45+
>x : number
46+
>x : number
47+
>{ x: 1 } : { x: number; }
48+
>x : number
49+
>1 : 1
50+
51+
({ a: [{ b: x }] } = { a: [{ b: 1 }] });
52+
>({ a: [{ b: x }] } = { a: [{ b: 1 }] }) : { a: [{ b: number; }]; }
53+
>{ a: [{ b: x }] } = { a: [{ b: 1 }] } : { a: [{ b: number; }]; }
54+
>{ a: [{ b: x }] } : { a: [{ b: number; }]; }
55+
>a : [{ b: number; }]
56+
>[{ b: x }] : [{ b: number; }]
57+
>{ b: x } : { b: number; }
58+
>b : number
59+
>x : number
60+
>{ a: [{ b: 1 }] } : { a: [{ b: number; }]; }
61+
>a : [{ b: number; }]
62+
>[{ b: 1 }] : [{ b: number; }]
63+
>{ b: 1 } : { b: number; }
64+
>b : number
65+
>1 : 1
66+
67+
({ x = 2 } = { x: b ? 1 : undefined });
68+
>({ x = 2 } = { x: b ? 1 : undefined }) : { x?: number | undefined; }
69+
>{ x = 2 } = { x: b ? 1 : undefined } : { x?: number | undefined; }
70+
>{ x = 2 } : { x?: number; }
71+
>x : number
72+
>2 : 2
73+
>{ x: b ? 1 : undefined } : { x?: number | undefined; }
74+
>x : number | undefined
75+
>b ? 1 : undefined : 1 | undefined
76+
>b : boolean
77+
>1 : 1
78+
>undefined : undefined
79+
80+
let used = 1;
81+
>used : number
82+
>1 : 1
83+
84+
({ x = used } = { x: b ? 1 : undefined });
85+
>({ x = used } = { x: b ? 1 : undefined }) : { x?: number | undefined; }
86+
>{ x = used } = { x: b ? 1 : undefined } : { x?: number | undefined; }
87+
>{ x = used } : { x?: number; }
88+
>x : number
89+
>used : number
90+
>{ x: b ? 1 : undefined } : { x?: number | undefined; }
91+
>x : number | undefined
92+
>b ? 1 : undefined : 1 | undefined
93+
>b : boolean
94+
>1 : 1
95+
>undefined : undefined
96+
2197
let y = 0;
2298
>y : number
2399
>0 : 0
24100

25101
// This is a write access to y, but not a write-*only* access.
26102
f(y++);
27103
>f(y++) : void
28-
>f : (x?: number) => void
104+
>f : (x?: number, b?: boolean) => void
29105
>y++ : number
30106
>y : number
31107

@@ -35,9 +111,12 @@ function f(x = 0) {
35111

36112
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
37113
>f(z = 1) : void
38-
>f : (x?: number) => void
114+
>f : (x?: number, b?: boolean) => void
39115
>z = 1 : 1
40116
>z : number
41117
>1 : 1
42118
}
119+
function f2(_: ReadonlyArray<number>): void {}
120+
>f2 : (_: ReadonlyArray<number>) => void
121+
>_ : ReadonlyArray<number>
43122

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
// @strict: true
12
// @noUnusedLocals: true
23
// @noUnusedParameters: true
34

4-
function f(x = 0) {
5+
function f(x = 0, b = false) {
6+
// None of these statements read from 'x', so it will be marked unused.
57
x = 1;
68
x++;
79
x /= 2;
10+
([x] = [1]);
11+
({ x } = { x: 1 });
12+
({ x: x } = { x: 1 });
13+
({ a: [{ b: x }] } = { a: [{ b: 1 }] });
14+
({ x = 2 } = { x: b ? 1 : undefined });
15+
let used = 1;
16+
({ x = used } = { x: b ? 1 : undefined });
817

918
let y = 0;
1019
// This is a write access to y, but not a write-*only* access.
@@ -13,3 +22,4 @@ function f(x = 0) {
1322
let z = 0;
1423
f(z = 1); // This effectively doesn't use `z`, values just pass through it.
1524
}
25+
function f2(_: ReadonlyArray<number>): void {}

0 commit comments

Comments
 (0)