Skip to content

Commit 1746465

Browse files
committed
feat(preview): Improve builtin extract to type / interface code actions to infer extracted type name from linked indentifier name. Disabled by default for now.
1 parent 0a74910 commit 1746465

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

src/configurationType.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,16 @@ export type Configuration = {
448448
* @default false
449449
*/
450450
displayAdditionalInfoInCompletions: boolean
451+
/**
452+
* Wether to try to infer name for extract type / interface code action
453+
* e.g. `let foo: { a: number }` -> `type FooType = { a: number }`
454+
*
455+
* Experimental and *will be enabled by default* once its:
456+
* 1. More configurable and rename is called
457+
* 2. Logic to avoid name conflicts
458+
* @default false
459+
*/
460+
'codeActions.extractTypeInferName': boolean
451461
}
452462

453463
// scrapped using search editor. config: caseInsesetive, context lines: 0, regex: const fix\w+ = "[^ ]+"

typescript/src/codeActions/decorateProxy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GetConfig } from '../types'
22
import getCodeActions, { REFACTORS_CATEGORY } from './getCodeActions'
3+
import improveBuiltin from './improveBuiltin'
34

45
export default (proxy: ts.LanguageService, languageService: ts.LanguageService, c: GetConfig) => {
56
proxy.getApplicableRefactors = (fileName, positionOrRange, preferences) => {
@@ -23,6 +24,8 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
2324
const { edit } = getCodeActions(sourceFile, positionOrRange, actionName)
2425
return edit
2526
}
26-
return languageService.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactorName, actionName, preferences)
27+
const prior = languageService.getEditsForRefactor(fileName, formatOptions, positionOrRange, refactorName, actionName, preferences)
28+
if (!prior) return
29+
return improveBuiltin(fileName, refactorName, actionName, languageService, c, prior) ?? prior
2730
}
2831
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { GetConfig } from '../types'
2+
import { findChildContainingExactPosition } from '../utils'
3+
4+
export default (
5+
fileName: string,
6+
refactorName: string,
7+
actionName: string,
8+
languageService: ts.LanguageService,
9+
c: GetConfig,
10+
prior: ts.RefactorEditInfo,
11+
): ts.RefactorEditInfo | undefined => {
12+
const extractToInterface = actionName === 'Extract to interface'
13+
if (c('codeActions.extractTypeInferName') && (actionName === 'Extract to type alias' || extractToInterface)) {
14+
const changeFirstEdit = (oldText: string, newTypeName: string) => {
15+
const startMarker = extractToInterface ? 'interface ' : 'type '
16+
const endMarker = extractToInterface ? ' {' : ' = '
17+
return oldText.slice(0, oldText.indexOf(startMarker) + startMarker.length) + newTypeName + oldText.slice(oldText.indexOf(endMarker))
18+
}
19+
20+
const fileEdit = prior.edits[0]!
21+
const { textChanges } = fileEdit
22+
23+
const sourceFile = languageService.getProgram()!.getSourceFile(fileName)!
24+
let node = findChildContainingExactPosition(sourceFile, textChanges[1]!.span.start - 1)
25+
if (!node) return
26+
if (ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) node = node.parent
27+
if (ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isPropertyAssignment(node) || ts.isPropertySignature(node)) {
28+
let isWithinType = ts.isPropertySignature(node)
29+
if (ts.isIdentifier(node.name)) {
30+
const identifierName = node.name.text
31+
if (!identifierName) return
32+
let typeName = identifierName[0]!.toUpperCase() + identifierName.slice(1)
33+
if (!isWithinType) typeName += 'Type'
34+
const newFileEdit: ts.FileTextChanges = {
35+
fileName,
36+
textChanges: textChanges.map((textChange, i) => {
37+
if (i === 0) return { ...textChange, newText: changeFirstEdit(textChange.newText, typeName) }
38+
/* if (i === 1) */ return { ...textChange, newText: typeName }
39+
}),
40+
}
41+
return {
42+
edits: [newFileEdit],
43+
}
44+
}
45+
}
46+
return prior
47+
}
48+
return
49+
}

0 commit comments

Comments
 (0)