Skip to content

Convert destruction #39832

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

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cde9b9f
wip
Kingwl Jul 30, 2020
3566774
wip
Kingwl Jul 30, 2020
deac7f6
add desruction refactor
Kingwl Jul 31, 2020
b146289
Add smart selection and unique name detection
Kingwl Jul 31, 2020
9c72d1c
add call and assignment detection
Kingwl Jul 31, 2020
de08600
fix incorrect convert
Kingwl Jul 31, 2020
bf481c1
fix lint
Kingwl Jul 31, 2020
7846923
add some tests
Kingwl Jul 31, 2020
fa63cbe
fix element access
Kingwl Jul 31, 2020
8465df6
fix element access collapse
Kingwl Jul 31, 2020
9f37ce9
Do not allow invalid identifier name
Kingwl Aug 3, 2020
87facd2
Fix hosting
Kingwl Aug 3, 2020
7133140
Add more cases
Kingwl Aug 3, 2020
bfb782a
refactor some code
Kingwl Aug 3, 2020
39d2ccb
Add array like destruction
Kingwl Aug 3, 2020
9fb1d62
Add more case
Kingwl Aug 3, 2020
5dcbc3f
Refactor common logic
Kingwl Aug 3, 2020
53a955b
Update description
Kingwl Aug 3, 2020
5a9faec
Add some comment test
Kingwl Aug 3, 2020
56a7d1d
Merge branch 'master' into convert_destruction
Kingwl Oct 28, 2020
f9a0828
update refactor behavior
Kingwl Oct 28, 2020
14daf2f
Merge branch 'master' into convert_destruction
Kingwl Jan 8, 2021
ad8349b
Add kind and support empty span
Kingwl Jan 8, 2021
9ac8cbc
Add support for notApplicableReason
Kingwl Jan 8, 2021
8ba4ad9
Rename all code
Kingwl Jan 8, 2021
12c73ae
Fix typo
Kingwl Jan 8, 2021
adba14f
Provide array to object destruction if invoked
Kingwl Jan 8, 2021
a66d51d
Avoid refactor if only one props or one ref
Kingwl Jan 8, 2021
71210ac
Care deeper for container
Kingwl Jan 8, 2021
76d31d0
Merge branch 'main' into convert_destruction
Kingwl Mar 16, 2022
38e874f
fix missing merge
Kingwl Mar 16, 2022
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
28 changes: 28 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7173,6 +7173,10 @@
"category": "Message",
"code": 95173
},
"Convert access expression to destruction": {
"category": "Message",
"code": 95174
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down Expand Up @@ -7293,5 +7297,29 @@
"A 'return' statement cannot be used inside a class static block.": {
"category": "Error",
"code": 18041
},
"No convertible access expression at location.": {
"category": "Error",
"code": 18042
},
"Cannot find convertible value declaration.": {
"category": "Error",
"code": 18043
},
"Cannot find convertible references.": {
"category": "Error",
"code": 18044
},
"Some references are un-convertible.": {
"category": "Error",
"code": 18045
},
"Too many empty array item.": {
"category": "Error",
"code": 18046
},
"At least '{0}' references are required.": {
"category": "Error",
"code": 18047
}
}
7 changes: 7 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3028,6 +3028,13 @@ namespace ts {
return skipOuterExpressions(node, flags);
}

export 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) {
Expand Down
429 changes: 429 additions & 0 deletions src/services/refactors/convertObjectDestruction.ts

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions src/services/refactors/extractType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ namespace ts.refactor {
return undefined;
}

function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean {
return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end);
}

function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined {
const result: TypeParameterDeclaration[] = [];
return visitor(selection) ? undefined : result;
Expand Down
29 changes: 29 additions & 0 deletions src/services/refactors/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,33 @@ namespace ts.refactor {
if(!requested) return true;
return known.substr(0, requested.length) === requested;
}

export const enum ResultStatus {
Ok,
Err
}

export interface OkResult<T> {
status: ResultStatus.Ok;
value: T;
}

export interface ErrResult {
status: ResultStatus.Err;
reason: string;
}

export type Result<T> = OkResult<T> | ErrResult;

export function isErrorResult<T>(result: Result<T>): result is ErrResult {
return result.status === ResultStatus.Err;
}

export function Err(reason: string): ErrResult {
return { status: ResultStatus.Err, reason };
}

export function Ok<T>(value: T): OkResult<T> {
return { status: ResultStatus.Ok, value };
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"codefixes/fixAddVoidToPromise.ts",
"refactors/convertExport.ts",
"refactors/convertImport.ts",
"refactors/convertObjectDestruction.ts",
"refactors/convertToOptionalChainExpression.ts",
"refactors/convertOverloadListToSingleSignature.ts",
"refactors/extractSymbol.ts",
Expand Down
7 changes: 7 additions & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,13 @@ namespace ts {
return start < end;
}

/**
* @internal
*/
export function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean {
return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end);
}

/**
* Assumes `candidate.start <= position` holds.
*/
Expand Down
18 changes: 18 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: 2
//// }
//// call(/*a*/item/*b*/.a, item.b)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: 2
}
const { a, b } = item
call(a, b)`,
});
16 changes: 16 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />

//// function foo (item: { a: string, b: number }) {
//// call(/*a*/item/*b*/.a, item.b)
Copy link
Contributor

Choose a reason for hiding this comment

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

Invoking the refactor with a selected here incorrectly gives this:

const { a } = item;
function foo (item: { a: string, b: number }) {
    call(a, item.b);
}

//// }

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `function foo (item: { a: string, b: number }) {
const { a, b } = item;
call(a, b)
}`,
});
18 changes: 18 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: 2
//// }
//// call(/*a*/item/*b*/["a"], item.b)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: 2
}
const { a, b } = item
call(a, b)`,
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: 2
//// }
//// const key = "a"
//// call(/*a*/item/*b*/[key], item.b)

goTo.select("a", "b");
verify.not.refactorAvailable("Convert to destruction")
20 changes: 20 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: 2
//// }
//// const a = false
//// call(/*a*/item/*b*/.a, item.b)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: 2
}
const a = false
const { a: a_1, b } = item
call(a_1, b)`,
});
28 changes: 28 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction14.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: {
//// c: {
//// d: 1,
//// e: 2
//// }
//// }
//// }
//// call(/*a*/item/*b*/.a, item.b, item.b.c, item.b.c.d, item.b.c.e)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: {
c: {
d: 1,
e: 2
}
}
}
const { a, b } = item
call(a, b, b.c, b.c.d, b.c.e)`,
});
29 changes: 29 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction15.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: {
//// c: {
//// d: 1,
//// e: 2
//// }
//// }
//// }
//// call(item.a, item.b, /*a*/item.b/*b*/.c, item.b.c.d, item.b.c.e)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: {
c: {
d: 1,
e: 2
}
}
}
const { c } = item.b
call(item.a, item.b, c, c.d, c.e)`,
});

28 changes: 28 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction16.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: {
//// c: {
//// d: 1,
//// e: 2
//// }
//// }
//// }
//// call(item.a, item.b, item.b.c, /*a*/item.b.c/*b*/.d, item.b.c.e)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: {
c: {
d: 1,
e: 2
}
}
}
const { d, e } = item.b.c
call(item.a, item.b, item.b.c, d, e)`,
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction17.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

//// interface A {
//// f: 1,
//// b: number
//// c: () => void
//// }
//// interface B {
//// f: 2,
//// b: number
//// c: () => string
//// }
//// declare const a: A | B
//// if (/*a*/a/*b*/.f === 1) {
//// a.b = 1
//// a.c()
//// } else {
//// a.b = 2
//// a.c()
//// }

goTo.select("a", "b");
verify.not.refactorAvailable("Convert to destruction")
43 changes: 43 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction18.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// <reference path='fourslash.ts' />

//// interface A {
//// f: 1,
//// b: number
//// c: () => void
//// }
//// interface B {
//// f: 2,
//// b: number
//// c: () => string
//// }
//// declare const a: A | B
//// if (/*a*/a/*b*/.f === 1) {
//// a.b = 1
//// } else {
//// a.b = 2
//// }

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
triggerReason: "invoked",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `interface A {
f: 1,
b: number
c: () => void
}
interface B {
f: 2,
b: number
c: () => string
}
declare const a: A | B
const { f } = a
if (f === 1) {
a.b = 1
} else {
a.b = 2
}`,
});
9 changes: 9 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction19.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// "a-a-a": 1, b: 2
//// }
//// call(/*a*/item/*b*/["a-a-a"], item.b)

goTo.select("a", "b");
verify.not.refactorAvailable("Convert to destruction")
18 changes: 18 additions & 0 deletions tests/cases/fourslash/refactorIntroduceDestruction2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />

//// const item = {
//// a: 1, b: 2
//// }
//// call(item.a, /*a*/item/*b*/.b)

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert to destruction",
actionName: "Convert to destruction",
actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message,
newContent: `const item = {
a: 1, b: 2
}
const { a, b } = item
call(a, b)`,
});
Loading