diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cf86150ee3b24..6f18d95a52985 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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", @@ -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 } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3366b214d094e..cb062689f8a45 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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) { diff --git a/src/services/refactors/convertObjectDestruction.ts b/src/services/refactors/convertObjectDestruction.ts new file mode 100644 index 0000000000000..ae487ac6450a4 --- /dev/null +++ b/src/services/refactors/convertObjectDestruction.ts @@ -0,0 +1,429 @@ +/* @internal */ +namespace ts.refactor { + const refactorName = "Convert to destruction"; + const refactorKind = "refactor.rewrite.expression.toDestructured"; + registerRefactor(refactorName, { getAvailableActions, getEditsForAction, kinds: [refactorKind] }); + + const convertToDestructionAction = { + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Convert_access_expression_to_destruction), + kind: refactorKind + }; + + function makeRefactorActionWithErrorReason(reason: string) { + return { ...convertToDestructionAction, notApplicableReason: reason }; + } + + function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, program, cancellationToken } = context; + const isJSFile = isSourceFileJS(file); + if (isJSFile || !cancellationToken) return emptyArray; + + const info = getInfo(context, file, program.getTypeChecker(), program, cancellationToken, /*resolveUniqueName*/ false, context.triggerReason === "invoked"); + if (!isErrorResult(info)) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Convert_access_expression_to_destruction), + actions: [convertToDestructionAction] + }]; + } + + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Convert_access_expression_to_destruction), + actions: [makeRefactorActionWithErrorReason(info.reason)] + }]; + } + return emptyArray; + } + + function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + Debug.assert(actionName === refactorName); + const { file, program, cancellationToken } = context; + const isJSFile = isSourceFileJS(file); + + const emptyResult: RefactorEditInfo = { edits: [] }; + if (isJSFile || !cancellationToken) return emptyResult; + + const info = getInfo(context, file, program.getTypeChecker(), program, cancellationToken, /*resolveUniqueName*/ true); + Debug.assert(!isErrorResult(info)); + + const edits = textChanges.ChangeTracker.with(context, t => doChange(t, file, info.value)); + return { renameFilename: undefined, renameLocation: undefined, edits }; + } + + function getUniqueNumericAccessVariable(name: string | number, file: SourceFile) { + const tempName = `index_${name}`; + return isFileLevelUniqueName(file, tempName) ? tempName : getUniqueName(tempName, file); + } + + /** + * `Dense` means we use array literal pattern to destruction the expression. + * We allowed index up to 15 to avoid many omit expression. + */ + function getDenseNumericAccessInfo(infos: ReferencedAccessInfo[]): [max: number, indexSet: Set] | undefined { + let min = Infinity; + let max = -Infinity; + const indexSet = new Set(); + for (const info of infos) { + if (!info.isNumericAccess) { + return undefined; + } + + // Check for integer. + const value = Number(info.name); + if (isNaN(value) || parseInt(value.toString(), 10) !== value) { + return undefined; + } + + min = Math.min(min, value); + max = Math.max(max, value); + indexSet.add(value); + + if (min < 0 || max < 0) { + return undefined; + } + } + + if (indexSet.size <= Math.floor(max / 2)) { + return undefined; + } + + return [max, indexSet]; + } + + function doChange(changeTracker: textChanges.ChangeTracker, file: SourceFile, info: Info) { + const bindingPattern = getBindingPattern(info, file, changeTracker); + Debug.assertIsDefined(bindingPattern); + + suppressLeadingAndTrailingTrivia(info.replacementExpression); + const newBinding = factory.createVariableStatement( + /* modifiers*/ undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + bindingPattern, + /*exclamationToken*/ undefined, + /*type*/ undefined, + info.replacementExpression + ) + ], + NodeFlags.Const + ), + ); + + if (info.firstReferencedStatement) { + changeTracker.insertNodeBefore( + file, + info.firstReferencedStatement, + newBinding + ); + } + } + + function getBindingPattern(info: Info, file: SourceFile, changeTracker: textChanges.ChangeTracker): BindingPattern | undefined { + const denseNumericInfo = info.isArrayLikeType && getDenseNumericAccessInfo(info.referencedAccessExpression); + if (denseNumericInfo) { + const [max, indexSet] = denseNumericInfo; + return getDenseNumericBindingPattern(info, file, max, indexSet, changeTracker); + } + return getObjectBindingPattern(info, file, changeTracker); + } + + function getObjectBindingPattern(info: Info, file: SourceFile, changeTracker: textChanges.ChangeTracker) { + const nameMap = new Map(); + const bindingElements: BindingElement[] = []; + info.referencedAccessExpression.forEach(({ expression, name, isNumericAccess }) => { + if (!nameMap.has(name)) { + const needAlias = isNumericAccess || info.namesNeedUniqueName.has(name); + const uniqueName = isNumericAccess ? getUniqueNumericAccessVariable(name, file) : + needAlias ? getUniqueName(name, file) : name; + nameMap.set(name, uniqueName); + bindingElements.push(getUniqueDestructionName(expression, needAlias, uniqueName)); + } + + const newName = nameMap.get(name); + Debug.assertIsDefined(newName); + replaceBindingPatternReferenced(file, changeTracker, expression, newName); + }); + return factory.createObjectBindingPattern(bindingElements); + } + + function getDenseNumericBindingPattern(info: Info, file: SourceFile, max: number, indexSet: Set, changeTracker: textChanges.ChangeTracker): ArrayBindingPattern { + const nameMap = new Map(); + const bindingElements: ArrayBindingElement[] = []; + + for (let i = 0; i <= max; ++i) { + if (indexSet.has(i)) { + if (!nameMap.has(i)) { + const name = getUniqueNumericAccessVariable(i, file); + nameMap.set(i, name); + } + + const name = nameMap.get(i); + Debug.assertIsDefined(name); + + bindingElements.push(factory.createBindingElement( + /* dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + factory.createIdentifier(name) + )); + } + else { + bindingElements.push(factory.createOmittedExpression()); + } + } + + info.referencedAccessExpression.forEach(({ expression, name }) => { + const index = parseInt(name); + + const newName = nameMap.get(index); + Debug.assertIsDefined(newName); + replaceBindingPatternReferenced(file, changeTracker, expression, newName); + }); + + return factory.createArrayBindingPattern(bindingElements); + } + + function replaceBindingPatternReferenced(file: SourceFile, changeTracker: textChanges.ChangeTracker, expression: Expression, newName: string) { + const newIdentifier = factory.createIdentifier(newName); + + changeTracker.replaceNode( + file, + expression, + newIdentifier + ); + } + + function getUniqueDestructionName(expr: AccessExpression, needAlias: boolean, newName: string) { + if (isPropertyAccessExpression(expr)) { + const bindingName = cast(expr.name, isIdentifier); + return factory.createBindingElement( + /* dotDotDotToken*/ undefined, + needAlias ? expr.name : undefined, + needAlias ? newName : bindingName, + ); + } + else { + const argExpr = cast(expr.argumentExpression, isPropertyName); + return factory.createBindingElement( + /* dotDotDotToken*/ undefined, + needAlias ? factory.cloneNode(argExpr) : undefined, + newName, + ); + } + } + + interface ReferencedAccessInfo { + expression: AccessExpression + name: string, + isNumericAccess: boolean + } + + interface Info { + replacementExpression: Expression; + referencedAccessExpression: ReferencedAccessInfo[]; + firstReferenced: Expression | undefined; + firstReferencedStatement: Statement | undefined; + namesNeedUniqueName: Set; + isArrayLikeType: boolean; + } + function getInfo(context: RefactorContext, file: SourceFile, checker: TypeChecker, program: Program, cancellationToken: CancellationToken, resolveUniqueName: boolean, triggerByInvoked = true): Result { + const current = getTokenAtPosition(file, context.startPosition); + const compilerOptions = context.program.getCompilerOptions(); + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const cursorRequest = range.pos === range.end && triggerByInvoked; + + const node = findAncestor(current, (node => node.parent && isAccessExpression(node.parent) && !rangeContainsSkipTrivia(range, node.parent, file) && ( + cursorRequest || nodeOverlapsWithStartEnd(node, file, range.pos, range.end) + ))); + + if (!node || !isAccessExpression(node.parent)) { + return Err(Diagnostics.No_convertible_access_expression_at_location.message); + } + + const isLeftOfAccess = node.parent.expression === node; + + const symbol = checker.getSymbolAtLocation(isLeftOfAccess ? node.parent.expression : node.parent); + if (!symbol || checker.isUnknownSymbol(symbol) || !symbol.valueDeclaration || !isVariableLike(symbol.valueDeclaration) || isEnumMember(symbol.valueDeclaration)) { + return Err(Diagnostics.Cannot_find_convertible_value_declaration.message); + } + + // Find only current file + const references = FindAllReferences.getReferenceEntriesForNode(-1, node, program, [file], cancellationToken); + let firstReferenced: Expression | undefined; + let firstReferencedStatement: Statement | undefined; + let hasNumericAccess = false; + const referencedAccessExpression: ReferencedAccessInfo[] = []; + const allReferencedAcccessExpression: AccessExpression[] = []; + const container = getContainingParameterDeclaration(symbol.valueDeclaration) || findAncestor(symbol.valueDeclaration, or(isStatement, isSourceFile)); + Debug.assertIsDefined(container); + forEach(references, reference => { + if (reference.kind !== FindAllReferences.EntryKind.Node) { + return; + } + + const accessExpression = getAccessExpressionIfValidReference(reference.node, symbol, container, allReferencedAcccessExpression, isLeftOfAccess); + if (!accessExpression) { + return; + } + + if (accessExpression !== symbol.valueDeclaration && isExpression(accessExpression)) { + if (!firstReferenced || accessExpression.pos < firstReferenced.pos) { + firstReferenced = accessExpression; + } + const referencedStatement = findAncestor(accessExpression, n => { + const parent = n.parent && isBlock(n.parent) && isFunctionLikeDeclaration(n.parent.parent) ? n.parent.parent : n.parent; + return isStatement(n) && parent === container.parent; + }); + if (referencedStatement && (!firstReferencedStatement || referencedStatement.pos < firstReferencedStatement.pos)) { + firstReferencedStatement = cast(referencedStatement, isStatement); + } + } + + if (isElementAccessExpression(accessExpression)) { + let isNumericAccess = false; + if (!isStringLiteralLike(accessExpression.argumentExpression) && !isNumericLiteral(accessExpression.argumentExpression)) { + return; + } + if (isNumericLiteral(accessExpression.argumentExpression)) { + hasNumericAccess = isNumericAccess = true; + } + else if (!isIdentifierText(accessExpression.argumentExpression.text, compilerOptions.target, compilerOptions.jsx ? LanguageVariant.JSX : LanguageVariant.Standard)) { + return; + } + + referencedAccessExpression.push({ + expression: accessExpression, + name: accessExpression.argumentExpression.text, + isNumericAccess + }); + return; + } + + referencedAccessExpression.push({ + expression: accessExpression, + name: accessExpression.name.text, + isNumericAccess: false + }); + }); + if (!firstReferenced || !firstReferencedStatement || !referencedAccessExpression.length || !some(referencedAccessExpression, ({ expression }) => rangeContainsRange(expression, current))) { + return Err(Diagnostics.Cannot_find_convertible_references.message); + } + + const minimumReferenceCount = 2; + if (!triggerByInvoked && referencedAccessExpression.length < minimumReferenceCount) { + return Err(formatMessage(/*_dummy*/ undefined, Diagnostics.At_least_0_references_are_required, minimumReferenceCount)); + } + + let hasUnconvertableReference = false; + const namesNeedUniqueName = new Set(); + const type = checker.getTypeOfSymbolAtLocation(symbol, firstReferenced); + const isArrayLikeType = checker.isArrayLikeType(type); + + if (hasNumericAccess && isArrayLikeType && !getDenseNumericAccessInfo(referencedAccessExpression) && !triggerByInvoked) { + return Err(Diagnostics.Too_many_empty_array_item.message); + } + + forEach(allReferencedAcccessExpression, expr => { + const referenceType = checker.getTypeAtLocation(expr.expression); + if (referenceType !== type) { + const accessType = checker.getTypeAtLocation(expr); + if (!isLeftOfAccess) { + if (accessType !== type) { + hasUnconvertableReference = true; + return "quit"; + } + } + else { + const propName = isElementAccessExpression(expr) ? + cast(expr.argumentExpression, isStringOrNumericLiteralLike).text : + checker.getSymbolAtLocation(expr)?.name; + + const prop = propName && checker.getPropertyOfType(type, propName); + + if (!prop || checker.getTypeOfSymbolAtLocation(prop, expr) !== accessType) { + hasUnconvertableReference = true; + return "quit"; + } + } + } + }); + if (hasUnconvertableReference) { + return Err(Diagnostics.Some_references_are_un_convertible.message); + } + + if (resolveUniqueName) { + forEach(referencedAccessExpression, ({ name }) => { + const symbol = checker.resolveName(name, /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (symbol) { + namesNeedUniqueName.add(name); + } + }); + } + + return Ok({ + replacementExpression: node.parent.expression, + firstReferenced, + firstReferencedStatement, + referencedAccessExpression, + namesNeedUniqueName, + isArrayLikeType + }); + } + + function getContainingParameterDeclaration(decl: Declaration) { + return findAncestor(decl, node => isFunctionLike(node) ? "quit" : isParameter(node)) as ParameterDeclaration | undefined; + } + + function getAccessExpressionIfValidReference(node: Node, symbol: Symbol, container: Node, allReferencedAcccessExpression: Node[], isLeftOfAccess: boolean): AccessExpression | undefined { + let lastChild = node; + const topReferencedAccessExpression = findAncestor(node.parent, n => { + if (isParenthesizedExpression(n)) { + lastChild = n; + return false; + } + else if (isAccessExpression(n)) { + if (isLeftOfAccess && n.expression === lastChild) { + return true; + } + else if (!isLeftOfAccess && (isPropertyAccessExpression(n) ? n.name === lastChild : n.argumentExpression === lastChild)) { + return true; + } + lastChild = n; + return false; + } + else if (isIdentifier(n) && isAccessExpression(n.parent)) { + lastChild = n; + return false; + } + return "quit"; + }); + if (!topReferencedAccessExpression) { + return undefined; + } + + const accessExpression = cast(topReferencedAccessExpression, isAccessExpression); + allReferencedAcccessExpression.push(accessExpression); + + if (isAssignmentTarget(accessExpression)) { + return undefined; + } + + const referencedParentMaybeCall = skipParenthesesUp(accessExpression.parent); + if (isCallOrNewExpression(referencedParentMaybeCall) && !(referencedParentMaybeCall.arguments && rangeContainsRange(referencedParentMaybeCall.arguments, topReferencedAccessExpression))) { + return undefined; + } + + Debug.assertIsDefined(symbol.valueDeclaration); + if (node.pos < symbol.valueDeclaration.pos) { + return undefined; + } + if (isFunctionLikeDeclaration(container.parent) && container.parent.body && !rangeContainsRange(container.parent.body, node)) { + return undefined; + } + + return accessExpression; + } +} \ No newline at end of file diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 0e16def63fe94..24cfa371fc034 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -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; diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index e86229af915d1..16719cf42ad08 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -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 { + status: ResultStatus.Ok; + value: T; + } + + export interface ErrResult { + status: ResultStatus.Err; + reason: string; + } + + export type Result = OkResult | ErrResult; + + export function isErrorResult(result: Result): result is ErrResult { + return result.status === ResultStatus.Err; + } + + export function Err(reason: string): ErrResult { + return { status: ResultStatus.Err, reason }; + } + + export function Ok(value: T): OkResult { + return { status: ResultStatus.Ok, value }; + } } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 3cd2188314613..74472f962f1d5 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -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", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 88836f59c3428..166b88a383cf3 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -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. */ diff --git a/tests/cases/fourslash/refactorIntroduceDestruction1.ts b/tests/cases/fourslash/refactorIntroduceDestruction1.ts new file mode 100644 index 0000000000000..ee1573cc1484b --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction1.ts @@ -0,0 +1,18 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction10.ts b/tests/cases/fourslash/refactorIntroduceDestruction10.ts new file mode 100644 index 0000000000000..b7612edc2da60 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction10.ts @@ -0,0 +1,16 @@ +/// + +//// function foo (item: { a: string, b: number }) { +//// 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: `function foo (item: { a: string, b: number }) { + const { a, b } = item; + call(a, b) +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction11.ts b/tests/cases/fourslash/refactorIntroduceDestruction11.ts new file mode 100644 index 0000000000000..7778e537c7619 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction11.ts @@ -0,0 +1,18 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction12.ts b/tests/cases/fourslash/refactorIntroduceDestruction12.ts new file mode 100644 index 0000000000000..5537d1f37dcbd --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction12.ts @@ -0,0 +1,10 @@ +/// + +//// 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") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction13.ts b/tests/cases/fourslash/refactorIntroduceDestruction13.ts new file mode 100644 index 0000000000000..4f59c8a8486fd --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction13.ts @@ -0,0 +1,20 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction14.ts b/tests/cases/fourslash/refactorIntroduceDestruction14.ts new file mode 100644 index 0000000000000..0d1512fc0b658 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction14.ts @@ -0,0 +1,28 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction15.ts b/tests/cases/fourslash/refactorIntroduceDestruction15.ts new file mode 100644 index 0000000000000..b8d2def8b4572 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction15.ts @@ -0,0 +1,29 @@ +/// + +//// 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)`, +}); + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction16.ts b/tests/cases/fourslash/refactorIntroduceDestruction16.ts new file mode 100644 index 0000000000000..33d813d6af3cf --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction16.ts @@ -0,0 +1,28 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction17.ts b/tests/cases/fourslash/refactorIntroduceDestruction17.ts new file mode 100644 index 0000000000000..e00a3ddad5ea2 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction17.ts @@ -0,0 +1,23 @@ +/// + +//// 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") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction18.ts b/tests/cases/fourslash/refactorIntroduceDestruction18.ts new file mode 100644 index 0000000000000..d29257d6e0d4d --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction18.ts @@ -0,0 +1,43 @@ +/// + +//// 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 +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction19.ts b/tests/cases/fourslash/refactorIntroduceDestruction19.ts new file mode 100644 index 0000000000000..812d7b3e5b135 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction19.ts @@ -0,0 +1,9 @@ +/// + +//// 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") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction2.ts b/tests/cases/fourslash/refactorIntroduceDestruction2.ts new file mode 100644 index 0000000000000..182e231b344fe --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction2.ts @@ -0,0 +1,18 @@ +/// + +//// 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)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction20.ts b/tests/cases/fourslash/refactorIntroduceDestruction20.ts new file mode 100644 index 0000000000000..81e990b49c9c3 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction20.ts @@ -0,0 +1,26 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// if (Math.random() < 0.5) { +//// call(/*a*/item/*b*/.a) +//// } else { +//// call(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 +if (Math.random() < 0.5) { + call(a) +} else { + call(b) +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction21.ts b/tests/cases/fourslash/refactorIntroduceDestruction21.ts new file mode 100644 index 0000000000000..ad027ed4c0e48 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction21.ts @@ -0,0 +1,20 @@ +/// + +//// function foo (item: { a: string, b: number }) { +//// function bar () { +//// 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: `function foo (item: { a: string, b: number }) { + const { a, b } = item; + function bar () { + call(a, b) + } +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction22.ts b/tests/cases/fourslash/refactorIntroduceDestruction22.ts new file mode 100644 index 0000000000000..15e9c1d54e613 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction22.ts @@ -0,0 +1,11 @@ +/// + +//// function foo () { +//// function bar () { +//// call(/*a*/item/*b*/.a, item.b) +//// } +//// const item = { a: 1, b: 2 } +//// } + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction23.ts b/tests/cases/fourslash/refactorIntroduceDestruction23.ts new file mode 100644 index 0000000000000..2f96021de8be2 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction23.ts @@ -0,0 +1,37 @@ +/// + +//// const item = { a: 1, b: 2 } +//// function foo() { +//// call(/*a*/item/*b*/.a) +//// } +//// function bar() { +//// call(item.b) +//// } +//// if (Math.max() < 0.5) { +//// call(item.a) +//// } +//// else { +//// call(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 +function foo() { + call(a) +} +function bar() { + call(b) +} +if (Math.max() < 0.5) { + call(a) +} +else { + call(b) +}`, +}); + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction24.ts b/tests/cases/fourslash/refactorIntroduceDestruction24.ts new file mode 100644 index 0000000000000..177100023cae6 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction24.ts @@ -0,0 +1,26 @@ +/// + +//// function foo () { +//// call(item.a) +//// } +//// const item = { a: 1, b: 2 } +//// function bar () { +//// call(/*a*/item/*b*/.a) +//// } +//// const aa = 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 () { + call(item.a) +} +const item = { a: 1, b: 2 } +const { a, b } = item +function bar () { + call(a) +} +const aa = b`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction25.ts b/tests/cases/fourslash/refactorIntroduceDestruction25.ts new file mode 100644 index 0000000000000..ab359a600ee81 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction25.ts @@ -0,0 +1,18 @@ +/// + +//// const item = { +//// a: 1, b: 2, c: 3 +//// } +//// call(/*a*/item/*b*/.a, item.a) + +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, c: 3 +} +const { a } = item +call(a, a)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction26.ts b/tests/cases/fourslash/refactorIntroduceDestruction26.ts new file mode 100644 index 0000000000000..f8349b470f0d8 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction26.ts @@ -0,0 +1,7 @@ +/// + +//// const f = (item: { a: 1, b: 2}) => /*a*/item/*b*/.a + item.b + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction") + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction27.ts b/tests/cases/fourslash/refactorIntroduceDestruction27.ts new file mode 100644 index 0000000000000..43f16f09eb265 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction27.ts @@ -0,0 +1,17 @@ +/// + +//// function f(item: { a: 1 }, b = item.a) { +//// call(/*a*/item/*b*/.a, b) +//// } + +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: `function f(item: { a: 1 }, b = item.a) { + const { a } = item; + call(a, b) +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction28.ts b/tests/cases/fourslash/refactorIntroduceDestruction28.ts new file mode 100644 index 0000000000000..6702c65a61962 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction28.ts @@ -0,0 +1,14 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[0], item[1], item[2]) + +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 = [ 1, 2, 3 ] as const +const [index_0, index_1, index_2] = item +call(index_0, index_1, index_2)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction29.ts b/tests/cases/fourslash/refactorIntroduceDestruction29.ts new file mode 100644 index 0000000000000..938c2474b6ccc --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction29.ts @@ -0,0 +1,14 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[0], item[2], item[2]) + +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 = [ 1, 2, 3 ] as const +const [index_0, , index_2] = item +call(index_0, index_2, index_2)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction3.ts b/tests/cases/fourslash/refactorIntroduceDestruction3.ts new file mode 100644 index 0000000000000..2d8fc07bcda0c --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction3.ts @@ -0,0 +1,9 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// call(/*a*/item.a/*b*/, item.b) + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction30.ts b/tests/cases/fourslash/refactorIntroduceDestruction30.ts new file mode 100644 index 0000000000000..8dc50f2196cd0 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction30.ts @@ -0,0 +1,15 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[0]) + +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: `const item = [ 1, 2, 3 ] as const +const [index_0] = item +call(index_0)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction31.ts b/tests/cases/fourslash/refactorIntroduceDestruction31.ts new file mode 100644 index 0000000000000..20573d940f438 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction31.ts @@ -0,0 +1,15 @@ +/// + +//// const item = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 ] as const +//// call(/*a*/item/*b*/[14], item[8], item[16]) + +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: `const item = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 ] as const +const { 14: index_14, 8: index_8, 16: index_16 } = item +call(index_14, index_8, index_16)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction32.ts b/tests/cases/fourslash/refactorIntroduceDestruction32.ts new file mode 100644 index 0000000000000..c83876a9f4b12 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction32.ts @@ -0,0 +1,20 @@ +/// + +//// interface A { +//// [k: string]: number +//// } +//// declare const foo: A +//// call(/*a*/foo/*b*/[0], foo[1]) + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to destruction", + actionName: "Convert to destruction", + actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message, + newContent: `interface A { + [k: string]: number +} +declare const foo: A +const { 0: index_0, 1: index_1 } = foo +call(index_0, index_1)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction33.ts b/tests/cases/fourslash/refactorIntroduceDestruction33.ts new file mode 100644 index 0000000000000..ebce88d4340f9 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction33.ts @@ -0,0 +1,30 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// call( +//// // l +//// /*a*/item/*b*/.a, +//// // t +//// 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( + // l + a, + // t + b +)`, +}); + + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction34.ts b/tests/cases/fourslash/refactorIntroduceDestruction34.ts new file mode 100644 index 0000000000000..5ce02c0d541fe --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction34.ts @@ -0,0 +1,21 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// item./*a*/a/*b*/ + +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: `const item = { + a: 1, b: 2 +} +const { a } = item +a`, +}); + + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction35.ts b/tests/cases/fourslash/refactorIntroduceDestruction35.ts new file mode 100644 index 0000000000000..48525f36e9dea --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction35.ts @@ -0,0 +1,30 @@ +/// + +//// const item = { +//// a: { +//// b: { +//// c: 1 +//// } +//// } +//// } +//// item./*a*/a/*b*/.b.c // right of item +//// item.a.b.c // another reference + +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: { + b: { + c: 1 + } + } +} +const { a } = item +a.b.c // right of item +a.b.c // another reference`, +}); + + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction36.ts b/tests/cases/fourslash/refactorIntroduceDestruction36.ts new file mode 100644 index 0000000000000..bd96cd3cbc5f1 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction36.ts @@ -0,0 +1,30 @@ +/// + +//// const item = { +//// a: { +//// b: { +//// c: 1 +//// } +//// } +//// } +//// /*a*/item.a/*b*/.b.c // left of b.c +//// item.a.b.c // another reference + +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: { + b: { + c: 1 + } + } +} +const { b } = item.a +b.c // left of b.c +b.c // another reference`, +}); + + diff --git a/tests/cases/fourslash/refactorIntroduceDestruction37.ts b/tests/cases/fourslash/refactorIntroduceDestruction37.ts new file mode 100644 index 0000000000000..26ee130bb982f --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction37.ts @@ -0,0 +1,16 @@ +/// + +//// interface I { foo: string; bar: number } +//// declare const a: I +//// console.log(a./*a*/foo/*b*/, a.bar, a.foo, a.bar) + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to destruction", + actionName: "Convert to destruction", + actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message, + newContent: `interface I { foo: string; bar: number } +declare const a: I +const { foo } = a +console.log(foo, a.bar, foo, a.bar)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction38.ts b/tests/cases/fourslash/refactorIntroduceDestruction38.ts new file mode 100644 index 0000000000000..35841747e3050 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction38.ts @@ -0,0 +1,16 @@ +/// + +//// interface I { foo: string; bar: number } +//// declare const a: I +//// console.log(/*a*/a/*b*/.foo, a.bar, a.foo, a.bar) + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to destruction", + actionName: "Convert to destruction", + actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message, + newContent: `interface I { foo: string; bar: number } +declare const a: I +const { foo, bar } = a +console.log(foo, bar, foo, bar)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction39.ts b/tests/cases/fourslash/refactorIntroduceDestruction39.ts new file mode 100644 index 0000000000000..473f043d1ff70 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction39.ts @@ -0,0 +1,19 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// call(it/*a*/em.a, item.b) + +goTo.marker('a'); +edit.applyRefactor({ + refactorName: "Convert to destruction", + actionName: "Convert to destruction", + actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message, + triggerReason: 'invoked', + newContent: `const item = { + a: 1, b: 2 +} +const { a, b } = item +call(a, b)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction4.ts b/tests/cases/fourslash/refactorIntroduceDestruction4.ts new file mode 100644 index 0000000000000..3062d38143a9a --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction4.ts @@ -0,0 +1,15 @@ +/// + +//// declare const u: { type: 'number'; payload: number } | { type: 'string', payload: string } +//// if(/*a*/u/*b*/.type === "number") { +//// /*c*/u/*d*/.payload.toExponential +//// } else { +//// /*e*/u/*f*/.payload.big +//// } + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction") +goTo.select("c", "d"); +verify.not.refactorAvailable("Convert to destruction") +goTo.select("e", "f"); +verify.not.refactorAvailable("Convert to destruction") \ No newline at end of file diff --git a/tests/cases/fourslash/refactorIntroduceDestruction40.ts b/tests/cases/fourslash/refactorIntroduceDestruction40.ts new file mode 100644 index 0000000000000..63619820f640b --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction40.ts @@ -0,0 +1,9 @@ +/// + +//// const item = { +//// a: 1, b: 2 +//// } +//// call(it/*a*/em.a, item.b) + +goTo.marker('a'); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorIntroduceDestruction41.ts b/tests/cases/fourslash/refactorIntroduceDestruction41.ts new file mode 100644 index 0000000000000..4019b52d1adf7 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction41.ts @@ -0,0 +1,15 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[3]) + +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: `const item = [ 1, 2, 3 ] as const +const { 3: index_3 } = item +call(index_3)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction42.ts b/tests/cases/fourslash/refactorIntroduceDestruction42.ts new file mode 100644 index 0000000000000..30ab388f0ec35 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction42.ts @@ -0,0 +1,15 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*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: `const item = [ 1, 2, 3 ] as const +const { 2: index_2 } = item +call(index_2)`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction43.ts b/tests/cases/fourslash/refactorIntroduceDestruction43.ts new file mode 100644 index 0000000000000..fd4751725617a --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction43.ts @@ -0,0 +1,7 @@ +/// + +//// const item = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 ] as const +//// call(/*a*/item/*b*/[14], item[8], item[16]) + +goTo.select("a", "b"); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction44.ts b/tests/cases/fourslash/refactorIntroduceDestruction44.ts new file mode 100644 index 0000000000000..8f665e80a9d9f --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction44.ts @@ -0,0 +1,7 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[3]) + +goTo.select("a", "b"); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction45.ts b/tests/cases/fourslash/refactorIntroduceDestruction45.ts new file mode 100644 index 0000000000000..20d23b8be6337 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction45.ts @@ -0,0 +1,7 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[2]) + +goTo.select("a", "b"); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction46.ts b/tests/cases/fourslash/refactorIntroduceDestruction46.ts new file mode 100644 index 0000000000000..0e75aaea46b4d --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction46.ts @@ -0,0 +1,8 @@ +/// + +//// function f(item: { a: 1 }, b = item.a) { +//// call(/*a*/item/*b*/.a, b) +//// } + +goTo.select("a", "b"); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction47.ts b/tests/cases/fourslash/refactorIntroduceDestruction47.ts new file mode 100644 index 0000000000000..8bada444b598a --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction47.ts @@ -0,0 +1,20 @@ +/// + +//// function f(item: { a: 1 }, b = item.a) { +//// call(/*a*/item/*b*/.a, b) +//// call(item.a, b) +//// } + + +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: `function f(item: { a: 1 }, b = item.a) { + const { a } = item + call(a, b) + call(a, b) +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction48.ts b/tests/cases/fourslash/refactorIntroduceDestruction48.ts new file mode 100644 index 0000000000000..7fbf99544f388 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction48.ts @@ -0,0 +1,7 @@ +/// + +//// const item = [ 1, 2, 3 ] as const +//// call(/*a*/item/*b*/[0]) + +goTo.select("a", "b"); +verify.not.refactorAvailableForTriggerReason('implicit', 'Convert to destruction'); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction49.ts b/tests/cases/fourslash/refactorIntroduceDestruction49.ts new file mode 100644 index 0000000000000..b770578d778a9 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction49.ts @@ -0,0 +1,17 @@ +/// + +//// function foo (item: { a: string, b: number }) { +//// call(item./*a*/a/*b*/, item.b) +//// } + +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: `function foo (item: { a: string, b: number }) { + const { a } = item; + call(a, item.b) +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction5.ts b/tests/cases/fourslash/refactorIntroduceDestruction5.ts new file mode 100644 index 0000000000000..202af42a3a79a --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction5.ts @@ -0,0 +1,22 @@ +/// + +//// declare const u: { type: 'number'; payload: number } | { type: 'string', payload: number } +//// if(/*a*/u/*b*/.type === "number") { +//// u.payload.toExponential +//// } else { +//// u.payload.toFixed +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to destruction", + actionName: "Convert to destruction", + actionDescription: ts.Diagnostics.Convert_access_expression_to_destruction.message, + newContent: `declare const u: { type: 'number'; payload: number } | { type: 'string', payload: number } +const { type, payload } = u +if(type === "number") { + payload.toExponential +} else { + payload.toFixed +}`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction6.ts b/tests/cases/fourslash/refactorIntroduceDestruction6.ts new file mode 100644 index 0000000000000..97a2caec0c453 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction6.ts @@ -0,0 +1,19 @@ +/// + +//// const item = { +//// a: 1, b: () => 1 +//// } +//// call(/*a*/item/*b*/.a, item.b()) + +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: `const item = { + a: 1, b: () => 1 +} +const { a } = item +call(a, item.b())`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction7.ts b/tests/cases/fourslash/refactorIntroduceDestruction7.ts new file mode 100644 index 0000000000000..e32be45eb3195 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction7.ts @@ -0,0 +1,21 @@ +/// + +//// const item = { +//// a: 1, b: () => 1 +//// } +//// item.a = 2 +//// call(/*a*/item/*b*/.a, item.b()) + +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: `const item = { + a: 1, b: () => 1 +} +item.a = 2 +const { a } = item +call(a, item.b())`, +}); diff --git a/tests/cases/fourslash/refactorIntroduceDestruction8.ts b/tests/cases/fourslash/refactorIntroduceDestruction8.ts new file mode 100644 index 0000000000000..b00b13b68e818 --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction8.ts @@ -0,0 +1,14 @@ +/// + +//// const item = { +//// a: 1, b: () => 1 +//// } +//// /*a*/item/*b*/.a = 2 +//// call(item.a, /*c*/item/*d*/.b(), (((/*e*/item/*f*/.b)))()) + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction") +goTo.select("c", "d"); +verify.not.refactorAvailable("Convert to destruction") +goTo.select("e", "f"); +verify.not.refactorAvailable("Introduce destruction") diff --git a/tests/cases/fourslash/refactorIntroduceDestruction9.ts b/tests/cases/fourslash/refactorIntroduceDestruction9.ts new file mode 100644 index 0000000000000..2bb1234f2890e --- /dev/null +++ b/tests/cases/fourslash/refactorIntroduceDestruction9.ts @@ -0,0 +1,10 @@ +/// + +//// enum E { +//// A, +//// B +//// } +//// /*a*/E/*b*/.A + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to destruction")