Skip to content

Commit

Permalink
feat(refactoring): add/from destructure bug fixes, this keyword sup…
Browse files Browse the repository at this point in the history
…port (#183)
  • Loading branch information
Ilanaya authored Nov 15, 2023
1 parent b7352ee commit 0b1c13f
Show file tree
Hide file tree
Showing 21 changed files with 950 additions and 924 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"fs-extra": "^10.1.0",
"got": "^12.5.3",
"got-cjs": "npm:got@^11.x",
"prettier": "3.1.0",
"tsm": "^2.3.0",
"type-fest": "^2.13.1",
"typed-jsonfile": "^0.2.1",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

174 changes: 0 additions & 174 deletions typescript/src/codeActions/custom/addDestructure.ts

This file was deleted.

49 changes: 49 additions & 0 deletions typescript/src/codeActions/custom/addDestructure/addDestructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getChangesTracker, isValidInitializerForDestructure } from '../../../utils'
import { CodeAction } from '../../getCodeActions'
import createDestructuredDeclaration from './createDestructuredDeclaration'
import addSplittedDestructure from './addSplittedDestructure'

export default {
id: 'addDestruct',
name: 'Add Destruct',
kind: 'refactor.rewrite.add-destruct',
tryToApply(sourceFile, position, _range, node, formatOptions, languageService) {
if (!node || !position) return
const initialDeclaration = ts.findAncestor(node, n => ts.isVariableDeclaration(n)) as ts.VariableDeclaration | undefined

if (initialDeclaration && !ts.isObjectBindingPattern(initialDeclaration.name)) {
const { initializer, type, name } = initialDeclaration

const result = addSplittedDestructure(node, sourceFile, formatOptions, languageService)

if (result) return result

if (!initializer || !isValidInitializerForDestructure(initializer)) return

const tracker = getChangesTracker(formatOptions ?? {})
const createdDeclaration = createDestructuredDeclaration(initializer, type, name)
if (createdDeclaration) {
tracker.replaceRange(
sourceFile,
{
pos: initialDeclaration.pos + initialDeclaration.getLeadingTriviaWidth(),
end: initialDeclaration.end,
},
createdDeclaration,
)

const changes = tracker.getChanges()
if (!changes) return undefined
return {
edits: [
{
fileName: sourceFile.fileName,
textChanges: changes[0]!.textChanges,
},
],
}
}
}
return addSplittedDestructure(node, sourceFile, formatOptions, languageService)
},
} satisfies CodeAction
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { findChildContainingExactPosition, getChangesTracker, getPositionHighlights, isValidInitializerForDestructure, makeUniqueName } from '../../../utils'

export default (node: ts.Node, sourceFile: ts.SourceFile, formatOptions: ts.FormatCodeSettings | undefined, languageService: ts.LanguageService) => {
const isValidInitializer = ts.isVariableDeclaration(node.parent) && node.parent.initializer && isValidInitializerForDestructure(node.parent.initializer)

// Make sure it only triggers on the destructuring object or parameter
if (!ts.isIdentifier(node) || !(isValidInitializer || ts.isParameter(node.parent))) return

const highlightPositions = getPositionHighlights(node.getStart(), sourceFile, languageService)

if (!highlightPositions) return
const tracker = getChangesTracker(formatOptions ?? {})

const propertyNames: Array<{ initial: string; unique: string | undefined }> = []
let nodeToReplaceWithBindingPattern: ts.Identifier | undefined

for (const pos of highlightPositions) {
const highlightedNode = findChildContainingExactPosition(sourceFile, pos)

if (!highlightedNode) continue

if (
ts.isElementAccessExpression(highlightedNode.parent) ||
ts.isCallExpression(highlightedNode.parent.parent) ||
ts.isTypeQueryNode(highlightedNode.parent)
)
return

if (ts.isIdentifier(highlightedNode) && ts.isPropertyAccessExpression(highlightedNode.parent)) {
const accessorName = highlightedNode.parent.name.getText()

if (!accessorName) continue

const uniqueName = makeUniqueName(accessorName, node, languageService, sourceFile)

propertyNames.push({ initial: accessorName, unique: uniqueName === accessorName ? undefined : uniqueName })
const range =
ts.isPropertyAssignment(highlightedNode.parent.parent) && highlightedNode.parent.parent.name.getText() === accessorName
? {
pos: highlightedNode.parent.parent.pos + highlightedNode.parent.parent.getLeadingTriviaWidth(),
end: highlightedNode.parent.parent.end,
}
: { pos, end: highlightedNode.parent.end }

tracker.replaceRangeWithText(sourceFile, range, uniqueName)
continue
}

if (ts.isIdentifier(highlightedNode) && (ts.isVariableDeclaration(highlightedNode.parent) || ts.isParameter(highlightedNode.parent))) {
// Already met a target node - abort as we encountered direct use of the potential destructured variable
if (nodeToReplaceWithBindingPattern) return
nodeToReplaceWithBindingPattern = highlightedNode
continue
}
}

if (!nodeToReplaceWithBindingPattern || propertyNames.length === 0) return

const bindings = propertyNames.map(({ initial, unique }) => {
return ts.factory.createBindingElement(undefined, unique ? initial : undefined, unique ?? initial)
})

const bindingPattern = ts.factory.createObjectBindingPattern(bindings)
const { pos, end } = nodeToReplaceWithBindingPattern

tracker.replaceRange(
sourceFile,
{
pos: pos + nodeToReplaceWithBindingPattern.getLeadingTriviaWidth(),
end,
},
bindingPattern,
)

const changes = tracker.getChanges()
if (!changes) return undefined
return {
edits: [
{
fileName: sourceFile.fileName,
textChanges: changes[0]!.textChanges,
},
],
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default (initializer: ts.Expression, type: ts.TypeNode | undefined, declarationName: ts.BindingName) => {
if (!ts.isPropertyAccessExpression(initializer)) return

const propertyName = initializer.name.text
const { factory } = ts

const bindingElement = factory.createBindingElement(
undefined,
declarationName.getText() === propertyName ? undefined : propertyName,
declarationName.getText(),
)

return factory.createVariableDeclaration(
factory.createObjectBindingPattern([bindingElement]),
undefined,
type ? factory.createTypeLiteralNode([factory.createPropertySignature(undefined, factory.createIdentifier(propertyName), undefined, type)]) : undefined,
initializer.expression,
)
}
Loading

0 comments on commit 0b1c13f

Please sign in to comment.