Skip to content

Commit b65558e

Browse files
committed
feat: New code actions: extract to arrow function above!
1 parent da7bb72 commit b65558e

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { GetConfig } from '../types'
2+
import { dedentString, findChildContainingExactPosition, findChildContainingPositionMaxDepth } from '../utils'
3+
4+
export const processApplicableRefactors = (refactor: ts.ApplicableRefactorInfo | undefined, c: GetConfig) => {
5+
if (!refactor) return
6+
const functionExtractors = refactor?.actions.filter(({ notApplicableReason }) => !notApplicableReason)
7+
if (functionExtractors?.length) {
8+
const kind = functionExtractors[0]!.kind!
9+
const blockScopeRefactor = functionExtractors.find(e => e.description.startsWith('Extract to inner function in'))
10+
if (blockScopeRefactor) {
11+
refactor!.actions.push({
12+
description: 'Extract to arrow function above',
13+
kind,
14+
name: `${blockScopeRefactor.name}_local_arrow`,
15+
})
16+
}
17+
const globalScopeRefactor = functionExtractors.find(e => e.description === 'Extract to function in global scope')
18+
if (globalScopeRefactor) {
19+
refactor!.actions.push({
20+
description: 'Extract to arrow function in global scope above',
21+
kind,
22+
name: `${globalScopeRefactor.name}_arrow`,
23+
})
24+
}
25+
}
26+
}
27+
28+
export const handleFunctionRefactorEdits = (
29+
actionName: string,
30+
31+
languageService: ts.LanguageService,
32+
fileName: string,
33+
formatOptions: ts.FormatCodeSettings,
34+
positionOrRange: number | ts.TextRange,
35+
refactorName: string,
36+
preferences: ts.UserPreferences | undefined,
37+
): ts.RefactorEditInfo | undefined => {
38+
if (!actionName.endsWith('_arrow')) return
39+
const originalAcitonName = actionName.replace('_local_arrow', '').replace('_arrow', '')
40+
const { edits, renameLocation, renameFilename } = languageService.getEditsForRefactor(
41+
fileName,
42+
formatOptions,
43+
positionOrRange,
44+
refactorName,
45+
originalAcitonName,
46+
preferences,
47+
)!
48+
// has random number of edits because imports can be added
49+
const { textChanges } = edits[0]!
50+
const functionChange = textChanges.at(-1)!
51+
functionChange.newText = functionChange.newText
52+
.replace(/function /, 'const ')
53+
.replace('(', ' = (')
54+
.replace(/\{\n/, '=> {\n')
55+
56+
const isLocal = actionName.endsWith('_local_arrow')
57+
// to think: maybe reuse ts getNodeToInsertPropertyBefore instead?
58+
const constantEdits = isLocal
59+
? languageService.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactorName, 'constant_scope_0', preferences)!.edits
60+
: undefined
61+
// local scope
62+
if (constantEdits) {
63+
const constantAdd = constantEdits[0]!.textChanges[0]!
64+
functionChange.span.start = constantAdd.span.start
65+
const indent = constantAdd.newText.match(/^\s*/)![0]
66+
// fix indent
67+
functionChange.newText = dedentString(functionChange.newText, indent, true) + '\n'
68+
}
69+
70+
// global scope
71+
if (!isLocal) {
72+
const lastNode = findChildContainingPositionMaxDepth(
73+
languageService.getProgram()!.getSourceFile(fileName)!,
74+
typeof positionOrRange === 'object' ? positionOrRange.pos : positionOrRange,
75+
2,
76+
)
77+
if (lastNode) {
78+
const pos = lastNode.pos + (lastNode.getFullText().match(/^\s+/)?.[0]?.length ?? 1) - 1
79+
functionChange.span.start = pos
80+
}
81+
}
82+
return {
83+
edits: [{ fileName, textChanges }],
84+
// TODO since ts making edit after current location, it doesn't expect renameLocation to be changed (lets fix it)
85+
// renameLocation: renameLocation! + functionChange.newText.length + 1,
86+
renameLocation: functionChange.span.start + functionChange.newText.indexOf('const ') + 'const '.length,
87+
renameFilename,
88+
}
89+
}

0 commit comments

Comments
 (0)