Skip to content

Commit 6c349d9

Browse files
committed
changeTrackers & codeActions improvements: apply some of changes lazily
1 parent 16973a6 commit 6c349d9

File tree

5 files changed

+43
-23
lines changed

5 files changed

+43
-23
lines changed

typescript/src/codeActions/custom/changeStringReplaceToRegex.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
import { CodeAction } from '../getCodeActions'
22
import escapeStringRegexp from 'escape-string-regexp'
3-
4-
const nodeToSpan = (node: ts.Node): ts.TextSpan => {
5-
const start = node.pos + (node.getLeadingTriviaWidth() ?? 0)
6-
return { start, length: node.end - start }
7-
}
3+
import { getChangesTracker } from '../../utils'
84

95
export default {
106
id: 'changeStringReplaceToRegex',
117
name: 'Change to Regex',
128
kind: 'refactor.rewrite.stringToRegex',
13-
tryToApply(sourceFile, position, _range, node) {
9+
tryToApply(sourceFile, position, _range, node, formatOptions) {
1410
if (!node || !position) return
1511
// requires full explicit object selection (be aware of comma) to not be annoying with suggestion
1612
if (!ts.isStringLiteral(node)) return
1713
if (!ts.isCallExpression(node.parent) || node.parent.arguments[0] !== node) return
1814
if (!ts.isPropertyAccessExpression(node.parent.expression)) return
1915
if (node.parent.expression.name.text !== 'replace') return
20-
// though it does to much escaping and ideally should be simplified
21-
const edits: ts.TextChange[] = [
22-
{
23-
span: nodeToSpan(node),
24-
newText: `/${escapeStringRegexp(node.text).replaceAll('\n', '\\n').replaceAll('\t', '\\t').replaceAll('\r', '\\r')}/`,
25-
},
26-
]
16+
if (!formatOptions) return true
17+
const changesTracker = getChangesTracker({})
18+
const { factory } = ts
19+
const replaceNode = factory.createRegularExpressionLiteral(
20+
// though it does to much escaping and ideally should be simplified
21+
`/${escapeStringRegexp(node.text).replaceAll('\n', '\\n').replaceAll('\t', '\\t').replaceAll('\r', '\\r')}/`,
22+
)
23+
24+
changesTracker.replaceNode(sourceFile, node, replaceNode)
2725
return {
2826
edits: [
2927
{
3028
fileName: sourceFile.fileName,
31-
textChanges: edits,
29+
textChanges: changesTracker.getChanges()[0]!.textChanges,
3230
},
3331
],
3432
}

typescript/src/codeActions/custom/objectSwapKeysAndValues.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,17 @@ export default {
2424
id: 'objectSwapKeysAndValues',
2525
name: 'Swap Keys and Values in Object',
2626
kind: 'refactor.rewrite.object.swapKeysAndValues',
27-
tryToApply(sourceFile, _position, range, node) {
27+
tryToApply(sourceFile, _position, range, node, formatOptions) {
2828
if (!range || !node) return
2929
// requires full explicit object selection (be aware of comma) to not be annoying with suggestion
30-
if (!approveCast(node, ts.isObjectLiteralExpression) || !(range.pos === node.pos + node.getLeadingTriviaWidth() && range.end === node.end)) return
30+
if (
31+
!approveCast(node, ts.isObjectLiteralExpression) ||
32+
!(range.pos === node.pos + node.getLeadingTriviaWidth() && range.end === node.end) ||
33+
node.properties.length === 0
34+
) {
35+
return
36+
}
37+
if (!formatOptions) return true
3138
const edits: ts.TextChange[] = []
3239
for (const property of node.properties) {
3340
if (!ts.isPropertyAssignment(property)) continue
@@ -48,7 +55,6 @@ export default {
4855
span: nodeToSpan(initializer),
4956
})
5057
}
51-
if (!edits.length) return undefined
5258
return {
5359
edits: [
5460
{

typescript/src/codeActions/getCodeActions.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ export type ApplyCodeAction = (
1616
position: number,
1717
range: ts.TextRange | undefined,
1818
node: ts.Node | undefined,
19-
formatOptions: ts.FormatCodeSettings,
19+
/** undefined when no edits is requested */
20+
formatOptions: ts.FormatCodeSettings | undefined,
2021
languageService: ts.LanguageService,
2122
languageServiceHost: ts.LanguageServiceHost,
22-
) => ts.RefactorEditInfo | SimplifiedRefactorInfo[] | undefined
23+
) => ts.RefactorEditInfo | SimplifiedRefactorInfo[] | true | undefined
2324

2425
export type CodeAction = {
2526
name: string
@@ -46,7 +47,8 @@ export default (
4647
const node = findChildContainingPosition(ts, sourceFile, pos)
4748
const appliableCodeActions = compact(
4849
codeActions.map(action => {
49-
const edits = action.tryToApply(sourceFile, pos, range, node, formatOptions ?? {}, languageService, languageServiceHost)
50+
const edits = action.tryToApply(sourceFile, pos, range, node, formatOptions, languageService, languageServiceHost)
51+
if (edits === true) return action
5052
if (!edits) return
5153
return {
5254
...action,
@@ -76,6 +78,7 @@ export default (
7678
}),
7779
)
7880

81+
const requestingEdit: any = requestingEditsId ? appliableCodeActions.find(({ id }) => id === requestingEditsId) : null
7982
return {
8083
info:
8184
(appliableCodeActions.length && {
@@ -89,6 +92,6 @@ export default (
8992
name: REFACTORS_CATEGORY,
9093
}) ||
9194
undefined,
92-
edit: requestingEditsId ? appliableCodeActions.find(({ id }) => id === requestingEditsId)!.edits : null!,
95+
edit: requestingEdit?.edits,
9396
}
9497
}

typescript/src/namespaceAutoImports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default (
6161
)
6262
const changeTracker = getChangesTracker(formatOptions)
6363
// todo respect sorting?
64-
changeTracker.insertNodeAtTopOfFile(sourceFile as FullSourceFile, importDeclaration as any, true)
64+
changeTracker.insertNodeAtTopOfFile(sourceFile, importDeclaration, true)
6565
const changes = changeTracker.getChanges()
6666
const { textChanges: importTextChanges } = changes[0]!
6767
textChanges.unshift(...importTextChanges)

typescript/src/utils.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,22 @@ export function addObjectMethodResultInterceptors<T extends Record<string, any>>
167167
}
168168
}
169169

170+
type OriginalTypeChecker = import('typescript-full').textChanges.ChangeTracker
171+
172+
type ChangeParameters<T extends any[]> = {
173+
[K in keyof T]: T[K] extends import('typescript-full').SourceFile ? ts.SourceFile : T[K] extends import('typescript-full').Node ? ts.Node : T[K]
174+
}
175+
176+
type ChangesTracker = {
177+
[K in keyof OriginalTypeChecker]: (...args: ChangeParameters<Parameters<OriginalTypeChecker[K]>> & any[]) => ReturnType<OriginalTypeChecker[K]>
178+
}
179+
170180
// have absolutely no idea why such useful utility is not publicly available
171181
export const getChangesTracker = formatOptions => {
172-
return new tsFull.textChanges.ChangeTracker(/* will be normalized by vscode anyway */ '\n', tsFull.formatting.getFormatContext(formatOptions, {}))
182+
return new tsFull.textChanges.ChangeTracker(
183+
/* will be normalized by vscode anyway */ '\n',
184+
tsFull.formatting.getFormatContext(formatOptions, {}),
185+
) as unknown as ChangesTracker
173186
}
174187

175188
export const dedentString = (string: string, addIndent = '', trimFirstLines = false) => {

0 commit comments

Comments
 (0)