Skip to content

Commit a32ebd5

Browse files
authored
feat(codeAction): rename parameter to name from type (#107)
1 parent 4738035 commit a32ebd5

File tree

4 files changed

+127
-5
lines changed

4 files changed

+127
-5
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { pipe, groupBy, map, compact } from 'lodash/fp'
2+
import { CodeAction } from '../getCodeActions'
3+
import extractType from '../../utils/extractType'
4+
5+
const getTypeParamName = (parameterName: string, parameterIndex: number, functionDeclaration: ts.Node, languageService: ts.LanguageService) => {
6+
const typeChecker = languageService.getProgram()!.getTypeChecker()
7+
const type = extractType(typeChecker, functionDeclaration)
8+
9+
const typeSignatureParams = typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call)[0]?.parameters
10+
if (!typeSignatureParams) return
11+
const typeSignatureParam = typeSignatureParams[parameterIndex]
12+
if (!typeSignatureParam) return
13+
const typeParamName = typeSignatureParam.name
14+
const isInternal = typeParamName.startsWith('__')
15+
if (isInternal || parameterName === typeParamName) return
16+
17+
return typeParamName
18+
}
19+
20+
const getEdits = (fileName: string, position: number, newText: string, languageService: ts.LanguageService): ts.FileTextChanges[] | undefined => {
21+
const renameLocations = languageService.findRenameLocations(fileName, position, false, false, {})
22+
if (!renameLocations) return
23+
24+
const extractFileName = ({ fileName }: ts.RenameLocation) => fileName
25+
26+
return pipe(
27+
groupBy(extractFileName),
28+
Object.entries,
29+
map(
30+
([fileName, changes]): ts.FileTextChanges => ({
31+
fileName,
32+
textChanges: changes.map(
33+
({ textSpan }): ts.TextChange => ({
34+
newText,
35+
span: textSpan,
36+
}),
37+
),
38+
}),
39+
),
40+
)(renameLocations)
41+
}
42+
43+
export const renameParameterToNameFromType = {
44+
id: 'renameParameterToNameFromType',
45+
name: '',
46+
kind: 'refactor.rewrite.renameParameterToNameFromType',
47+
tryToApply(sourceFile, position, range, node, formatOptions, languageService) {
48+
if (!node || !position) return
49+
const functionSignature = node.parent.parent
50+
if (
51+
!ts.isIdentifier(node) ||
52+
!ts.isParameter(node.parent) ||
53+
!ts.isFunctionLike(functionSignature) ||
54+
!ts.isVariableDeclaration(functionSignature.parent)
55+
)
56+
return
57+
const { parent: functionDecl, parameters: functionParameters } = functionSignature
58+
59+
const parameterIndex = functionParameters.indexOf(node.parent)
60+
const parameterName = functionParameters[parameterIndex]!.name.getText()
61+
const typeParamName = getTypeParamName(parameterName, functionParameters.indexOf(node.parent), functionDecl, languageService)
62+
if (!typeParamName) return
63+
this.name = `Rename Parameter to Name from Type '${functionDecl.type!.getText()}'`
64+
if (!formatOptions) return true
65+
66+
const edits = compact(getEdits(sourceFile.fileName, position, typeParamName, languageService))
67+
return {
68+
edits,
69+
}
70+
},
71+
} satisfies CodeAction
72+
73+
export const renameAllParametersToNameFromType = {
74+
id: 'renameAllParametersToNameFromType',
75+
name: '',
76+
kind: 'refactor.rewrite.renameAllParametersToNameFromType',
77+
tryToApply(sourceFile, position, range, node, formatOptions, languageService) {
78+
if (!node || !position) return
79+
const functionSignature = node.parent.parent
80+
if (
81+
!ts.isIdentifier(node) ||
82+
!ts.isParameter(node.parent) ||
83+
!ts.isFunctionLike(functionSignature) ||
84+
!ts.isVariableDeclaration(functionSignature.parent)
85+
)
86+
return
87+
const { parent: functionDecl, parameters: functionParameters } = functionSignature
88+
const paramsToRename = compact(
89+
functionParameters.map((functionParameter, index) => {
90+
const typeParamName = getTypeParamName(functionParameter.getText(), index, functionDecl, languageService)
91+
if (!typeParamName) return
92+
return { param: functionParameter, typeParamName }
93+
}),
94+
)
95+
96+
if (paramsToRename.length < 2) return
97+
98+
this.name = `Rename All Parameters to Name from Type '${functionDecl.type!.getText()}'`
99+
if (!formatOptions) return true
100+
101+
const edits = compact(
102+
paramsToRename.flatMap(({ param, typeParamName }) => {
103+
return getEdits(sourceFile.fileName, param.getStart(), typeParamName, languageService)
104+
}),
105+
)
106+
return {
107+
edits,
108+
}
109+
},
110+
} satisfies CodeAction

typescript/src/codeActions/getCodeActions.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ import objectSwapKeysAndValues from './custom/objectSwapKeysAndValues'
66
import changeStringReplaceToRegex from './custom/changeStringReplaceToRegex'
77
import splitDeclarationAndInitialization from './custom/splitDeclarationAndInitialization'
88
import addMissingProperties from './extended/addMissingProperties'
9+
import { renameParameterToNameFromType, renameAllParametersToNameFromType } from './custom/renameParameterToNameFromType'
910

10-
const codeActions: CodeAction[] = [objectSwapKeysAndValues, changeStringReplaceToRegex, splitDeclarationAndInitialization]
11+
const codeActions: CodeAction[] = [
12+
objectSwapKeysAndValues,
13+
changeStringReplaceToRegex,
14+
splitDeclarationAndInitialization,
15+
renameParameterToNameFromType,
16+
renameAllParametersToNameFromType,
17+
]
1118
const extendedCodeActions: ExtendedCodeAction[] = [addMissingProperties]
1219

1320
type SimplifiedRefactorInfo =

typescript/src/constructMethodSnippet.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { compact, oneOf } from '@zardoy/utils'
22
import { isTypeNode } from './completions/keywordsSpace'
33
import { GetConfig } from './types'
44
import { findChildContainingExactPosition } from './utils'
5+
import extractType from './utils/extractType'
56

67
// todo-low-ee inspect any last arg infer
78
export default (
@@ -17,11 +18,9 @@ export default (
1718
) => {
1819
let containerNode = findChildContainingExactPosition(sourceFile, position)
1920
if (!containerNode || isTypeNode(containerNode)) return
20-
2121
const checker = languageService.getProgram()!.getTypeChecker()!
22-
let type = symbol ? checker.getTypeOfSymbol(symbol) : checker.getTypeAtLocation(containerNode)
23-
// give another chance
24-
if (symbol && type['intrinsicName'] === 'error') type = checker.getTypeOfSymbolAtLocation(symbol, containerNode)
22+
23+
const type = extractType(checker, containerNode, symbol)
2524

2625
if (ts.isIdentifier(containerNode)) containerNode = containerNode.parent
2726
if (ts.isPropertyAccessExpression(containerNode)) containerNode = containerNode.parent

typescript/src/utils/extractType.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default (typeChecker: ts.TypeChecker, node: ts.Node, symbol?: ts.Symbol) => {
2+
const type = symbol ? typeChecker.getTypeOfSymbol(symbol) : typeChecker.getTypeAtLocation(node)
3+
// give another chance
4+
if (symbol && type['intrinsicName'] === 'error') return typeChecker.getTypeOfSymbolAtLocation(symbol, node)
5+
return type
6+
}

0 commit comments

Comments
 (0)