diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 87cf26a1927e8..783911d6dea93 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2331,6 +2331,13 @@ namespace ts { return node; } + function skipParenthesesUp(node: Node): Node { + while (node.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return node; + } + // a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped export function isDeleteTarget(node: Node): boolean { if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { @@ -4206,6 +4213,8 @@ namespace ts { if (!parent) return AccessKind.Read; switch (parent.kind) { + case SyntaxKind.ParenthesizedExpression: + return accessKind(parent); case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.PrefixUnaryExpression: const { operator } = parent as PrefixUnaryExpression | PostfixUnaryExpression; @@ -4217,13 +4226,35 @@ namespace ts { : AccessKind.Read; case SyntaxKind.PropertyAccessExpression: return (parent as PropertyAccessExpression).name !== node ? AccessKind.Read : accessKind(parent); + case SyntaxKind.PropertyAssignment: { + const parentAccess = accessKind(parent.parent); + // In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write. + return node === (parent as PropertyAssignment).name ? reverseAccessKind(parentAccess) : parentAccess; + } + case SyntaxKind.ShorthandPropertyAssignment: + // Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals. + return node === (parent as ShorthandPropertyAssignment).objectAssignmentInitializer ? AccessKind.Read : accessKind(parent.parent); + case SyntaxKind.ArrayLiteralExpression: + return accessKind(parent); default: return AccessKind.Read; } function writeOrReadWrite(): AccessKind { // If grandparent is not an ExpressionStatement, this is used as an expression in addition to having a side effect. - return parent.parent && parent.parent.kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite; + return parent.parent && skipParenthesesUp(parent.parent).kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite; + } + } + function reverseAccessKind(a: AccessKind): AccessKind { + switch (a) { + case AccessKind.Read: + return AccessKind.Write; + case AccessKind.Write: + return AccessKind.Read; + case AccessKind.ReadWrite: + return AccessKind.ReadWrite; + default: + return Debug.assertNever(a); } } diff --git a/tests/baselines/reference/noUnusedLocals_writeOnly.errors.txt b/tests/baselines/reference/noUnusedLocals_writeOnly.errors.txt index 09ebea0f654a1..347e181d94627 100644 --- a/tests/baselines/reference/noUnusedLocals_writeOnly.errors.txt +++ b/tests/baselines/reference/noUnusedLocals_writeOnly.errors.txt @@ -1,14 +1,22 @@ tests/cases/compiler/noUnusedLocals_writeOnly.ts(1,12): error TS6133: 'x' is declared but its value is never read. -tests/cases/compiler/noUnusedLocals_writeOnly.ts(10,9): error TS6133: 'z' is declared but its value is never read. +tests/cases/compiler/noUnusedLocals_writeOnly.ts(18,9): error TS6133: 'z' is declared but its value is never read. ==== tests/cases/compiler/noUnusedLocals_writeOnly.ts (2 errors) ==== - function f(x = 0) { + function f(x = 0, b = false) { ~ !!! error TS6133: 'x' is declared but its value is never read. + // None of these statements read from 'x', so it will be marked unused. x = 1; x++; x /= 2; + ([x] = [1]); + ({ x } = { x: 1 }); + ({ x: x } = { x: 1 }); + ({ a: [{ b: x }] } = { a: [{ b: 1 }] }); + ({ x = 2 } = { x: b ? 1 : undefined }); + let used = 1; + ({ x = used } = { x: b ? 1 : undefined }); let y = 0; // 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 !!! error TS6133: 'z' is declared but its value is never read. f(z = 1); // This effectively doesn't use `z`, values just pass through it. } + function f2(_: ReadonlyArray): void {} \ No newline at end of file diff --git a/tests/baselines/reference/noUnusedLocals_writeOnly.js b/tests/baselines/reference/noUnusedLocals_writeOnly.js index bc0077930423f..55a927bfc2b5a 100644 --- a/tests/baselines/reference/noUnusedLocals_writeOnly.js +++ b/tests/baselines/reference/noUnusedLocals_writeOnly.js @@ -1,8 +1,16 @@ //// [noUnusedLocals_writeOnly.ts] -function f(x = 0) { +function f(x = 0, b = false) { + // None of these statements read from 'x', so it will be marked unused. x = 1; x++; x /= 2; + ([x] = [1]); + ({ x } = { x: 1 }); + ({ x: x } = { x: 1 }); + ({ a: [{ b: x }] } = { a: [{ b: 1 }] }); + ({ x = 2 } = { x: b ? 1 : undefined }); + let used = 1; + ({ x = used } = { x: b ? 1 : undefined }); let y = 0; // This is a write access to y, but not a write-*only* access. @@ -11,17 +19,30 @@ function f(x = 0) { let z = 0; f(z = 1); // This effectively doesn't use `z`, values just pass through it. } +function f2(_: ReadonlyArray): void {} //// [noUnusedLocals_writeOnly.js] -function f(x) { +"use strict"; +function f(x, b) { if (x === void 0) { x = 0; } + if (b === void 0) { b = false; } + var _a, _b; + // None of these statements read from 'x', so it will be marked unused. x = 1; x++; x /= 2; + (x = [1][0]); + (x = { x: 1 }.x); + (x = { x: 1 }.x); + (x = { a: [{ b: 1 }] }.a[0].b); + (_a = { x: b ? 1 : undefined }.x, x = _a === void 0 ? 2 : _a); + var used = 1; + (_b = { x: b ? 1 : undefined }.x, x = _b === void 0 ? used : _b); var y = 0; // This is a write access to y, but not a write-*only* access. f(y++); var z = 0; f(z = 1); // This effectively doesn't use `z`, values just pass through it. } +function f2(_) { } diff --git a/tests/baselines/reference/noUnusedLocals_writeOnly.symbols b/tests/baselines/reference/noUnusedLocals_writeOnly.symbols index afb13f9aa2cb9..a794291cadd79 100644 --- a/tests/baselines/reference/noUnusedLocals_writeOnly.symbols +++ b/tests/baselines/reference/noUnusedLocals_writeOnly.symbols @@ -1,8 +1,10 @@ === tests/cases/compiler/noUnusedLocals_writeOnly.ts === -function f(x = 0) { +function f(x = 0, b = false) { >f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0)) >x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) +>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17)) + // None of these statements read from 'x', so it will be marked unused. x = 1; >x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) @@ -12,19 +14,58 @@ function f(x = 0) { x /= 2; >x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) + ([x] = [1]); +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) + + ({ x } = { x: 1 }); +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 6, 6)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 6, 14)) + + ({ x: x } = { x: 1 }); +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 7, 6)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 7, 17)) + + ({ a: [{ b: x }] } = { a: [{ b: 1 }] }); +>a : Symbol(a, Decl(noUnusedLocals_writeOnly.ts, 8, 6)) +>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 8, 12)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 0, 11)) +>a : Symbol(a, Decl(noUnusedLocals_writeOnly.ts, 8, 26)) +>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 8, 32)) + + ({ x = 2 } = { x: b ? 1 : undefined }); +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 9, 6)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 9, 18)) +>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17)) +>undefined : Symbol(undefined) + + let used = 1; +>used : Symbol(used, Decl(noUnusedLocals_writeOnly.ts, 10, 7)) + + ({ x = used } = { x: b ? 1 : undefined }); +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 11, 6)) +>used : Symbol(used, Decl(noUnusedLocals_writeOnly.ts, 10, 7)) +>x : Symbol(x, Decl(noUnusedLocals_writeOnly.ts, 11, 21)) +>b : Symbol(b, Decl(noUnusedLocals_writeOnly.ts, 0, 17)) +>undefined : Symbol(undefined) + let y = 0; ->y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 5, 7)) +>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 13, 7)) // This is a write access to y, but not a write-*only* access. f(y++); >f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0)) ->y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 5, 7)) +>y : Symbol(y, Decl(noUnusedLocals_writeOnly.ts, 13, 7)) let z = 0; ->z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 9, 7)) +>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 17, 7)) f(z = 1); // This effectively doesn't use `z`, values just pass through it. >f : Symbol(f, Decl(noUnusedLocals_writeOnly.ts, 0, 0)) ->z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 9, 7)) +>z : Symbol(z, Decl(noUnusedLocals_writeOnly.ts, 17, 7)) } +function f2(_: ReadonlyArray): void {} +>f2 : Symbol(f2, Decl(noUnusedLocals_writeOnly.ts, 19, 1)) +>_ : Symbol(_, Decl(noUnusedLocals_writeOnly.ts, 20, 12)) +>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --)) diff --git a/tests/baselines/reference/noUnusedLocals_writeOnly.types b/tests/baselines/reference/noUnusedLocals_writeOnly.types index 4b90d7b13e71e..43a393a1edd32 100644 --- a/tests/baselines/reference/noUnusedLocals_writeOnly.types +++ b/tests/baselines/reference/noUnusedLocals_writeOnly.types @@ -1,9 +1,12 @@ === tests/cases/compiler/noUnusedLocals_writeOnly.ts === -function f(x = 0) { ->f : (x?: number) => void +function f(x = 0, b = false) { +>f : (x?: number, b?: boolean) => void >x : number >0 : 0 +>b : boolean +>false : false + // None of these statements read from 'x', so it will be marked unused. x = 1; >x = 1 : 1 >x : number @@ -18,6 +21,79 @@ function f(x = 0) { >x : number >2 : 2 + ([x] = [1]); +>([x] = [1]) : [number] +>[x] = [1] : [number] +>[x] : [number] +>x : number +>[1] : [number] +>1 : 1 + + ({ x } = { x: 1 }); +>({ x } = { x: 1 }) : { x: number; } +>{ x } = { x: 1 } : { x: number; } +>{ x } : { x: number; } +>x : number +>{ x: 1 } : { x: number; } +>x : number +>1 : 1 + + ({ x: x } = { x: 1 }); +>({ x: x } = { x: 1 }) : { x: number; } +>{ x: x } = { x: 1 } : { x: number; } +>{ x: x } : { x: number; } +>x : number +>x : number +>{ x: 1 } : { x: number; } +>x : number +>1 : 1 + + ({ a: [{ b: x }] } = { a: [{ b: 1 }] }); +>({ a: [{ b: x }] } = { a: [{ b: 1 }] }) : { a: [{ b: number; }]; } +>{ a: [{ b: x }] } = { a: [{ b: 1 }] } : { a: [{ b: number; }]; } +>{ a: [{ b: x }] } : { a: [{ b: number; }]; } +>a : [{ b: number; }] +>[{ b: x }] : [{ b: number; }] +>{ b: x } : { b: number; } +>b : number +>x : number +>{ a: [{ b: 1 }] } : { a: [{ b: number; }]; } +>a : [{ b: number; }] +>[{ b: 1 }] : [{ b: number; }] +>{ b: 1 } : { b: number; } +>b : number +>1 : 1 + + ({ x = 2 } = { x: b ? 1 : undefined }); +>({ x = 2 } = { x: b ? 1 : undefined }) : { x?: number | undefined; } +>{ x = 2 } = { x: b ? 1 : undefined } : { x?: number | undefined; } +>{ x = 2 } : { x?: number; } +>x : number +>2 : 2 +>{ x: b ? 1 : undefined } : { x?: number | undefined; } +>x : number | undefined +>b ? 1 : undefined : 1 | undefined +>b : boolean +>1 : 1 +>undefined : undefined + + let used = 1; +>used : number +>1 : 1 + + ({ x = used } = { x: b ? 1 : undefined }); +>({ x = used } = { x: b ? 1 : undefined }) : { x?: number | undefined; } +>{ x = used } = { x: b ? 1 : undefined } : { x?: number | undefined; } +>{ x = used } : { x?: number; } +>x : number +>used : number +>{ x: b ? 1 : undefined } : { x?: number | undefined; } +>x : number | undefined +>b ? 1 : undefined : 1 | undefined +>b : boolean +>1 : 1 +>undefined : undefined + let y = 0; >y : number >0 : 0 @@ -25,7 +101,7 @@ function f(x = 0) { // This is a write access to y, but not a write-*only* access. f(y++); >f(y++) : void ->f : (x?: number) => void +>f : (x?: number, b?: boolean) => void >y++ : number >y : number @@ -35,9 +111,12 @@ function f(x = 0) { f(z = 1); // This effectively doesn't use `z`, values just pass through it. >f(z = 1) : void ->f : (x?: number) => void +>f : (x?: number, b?: boolean) => void >z = 1 : 1 >z : number >1 : 1 } +function f2(_: ReadonlyArray): void {} +>f2 : (_: ReadonlyArray) => void +>_ : ReadonlyArray diff --git a/tests/cases/compiler/noUnusedLocals_writeOnly.ts b/tests/cases/compiler/noUnusedLocals_writeOnly.ts index ca1df86991596..8d2de0d517c71 100644 --- a/tests/cases/compiler/noUnusedLocals_writeOnly.ts +++ b/tests/cases/compiler/noUnusedLocals_writeOnly.ts @@ -1,10 +1,19 @@ +// @strict: true // @noUnusedLocals: true // @noUnusedParameters: true -function f(x = 0) { +function f(x = 0, b = false) { + // None of these statements read from 'x', so it will be marked unused. x = 1; x++; x /= 2; + ([x] = [1]); + ({ x } = { x: 1 }); + ({ x: x } = { x: 1 }); + ({ a: [{ b: x }] } = { a: [{ b: 1 }] }); + ({ x = 2 } = { x: b ? 1 : undefined }); + let used = 1; + ({ x = used } = { x: b ? 1 : undefined }); let y = 0; // This is a write access to y, but not a write-*only* access. @@ -13,3 +22,4 @@ function f(x = 0) { let z = 0; f(z = 1); // This effectively doesn't use `z`, values just pass through it. } +function f2(_: ReadonlyArray): void {}