Skip to content

Commit 08952ab

Browse files
committed
fix: preserve passing formatOptions to TS completions
feat: new fix for builtin Code Fix on ctrl+s (fix all): add missing await now works correctly (only one async is added if have several awaits)
1 parent 53152d1 commit 08952ab

File tree

9 files changed

+103
-27
lines changed

9 files changed

+103
-27
lines changed

README.MD

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,20 @@ Mark all TS code actions with `🔵`, so you can be sure they're coming from Typ
164164
type A<T extends 'foo' | 'bar' = ''> = ...
165165
```
166166
167-
### Builtin CodeFix Fixes
167+
### Builtin Code Fix Fixes
168+
169+
With this plugin fixes some builtin code actions and these code fixes will work *correctly*:
170+
171+
```ts
172+
// ctrl+s fix: only one async is added
173+
const syncFunction = () => {
174+
await fetch()
175+
await fetch()
176+
}
177+
const a = 5
178+
// const to let fix
179+
a = 6
180+
```
168181

169182
## Special Commands List
170183

src/specialCommands.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { defaultJsSupersetLangsWithVue } from '@zardoy/vscode-utils/build/langs'
88
import { offsetPosition } from '@zardoy/vscode-utils/build/position'
99
import { RequestOptionsTypes, RequestResponseTypes } from '../typescript/src/ipcTypes'
1010
import { sendCommand } from './sendCommand'
11-
import { tsRangeToVscode, tsRangeToVscodeSelection } from './util'
11+
import { tsRangeToVscode, tsRangeToVscodeSelection, tsTextChangesToVcodeTextEdits } from './util'
1212

1313
export default () => {
1414
registerExtensionCommand('removeFunctionArgumentsTypesInSelection', async () => {
@@ -260,23 +260,27 @@ export default () => {
260260
const editor = vscode.window.activeTextEditor!
261261
const edits = await sendTurnIntoArrayRequest<RequestResponseTypes['turnArrayIntoObjectEdit']>(editor.selection, selectedKey)
262262
if (!edits) throw new Error('Unknown error. Try debug.')
263-
await editor.edit(builder => {
264-
for (const { span, newText } of edits) {
265-
const start = editor.document.positionAt(span.start)
266-
builder.replace(new vscode.Range(start, offsetPosition(editor.document, start, span.length)), newText)
267-
}
268-
})
263+
const edit = new vscode.WorkspaceEdit()
264+
edit.set(editor.document.uri, tsTextChangesToVcodeTextEdits(editor.document, edits))
265+
await vscode.workspace.applyEdit(edit)
269266
})
270267

271268
// its actually a code action, but will be removed from there soon
272269
vscode.languages.registerCodeActionsProvider(defaultJsSupersetLangsWithVue, {
273270
async provideCodeActions(document, range, context, token) {
274-
if (
275-
context.triggerKind !== vscode.CodeActionTriggerKind.Invoke ||
276-
document !== vscode.window.activeTextEditor?.document ||
277-
!getExtensionSetting('enablePlugin')
278-
)
271+
if (document !== vscode.window.activeTextEditor?.document || !getExtensionSetting('enablePlugin')) {
279272
return
273+
}
274+
275+
if (context.only?.contains(vscode.CodeActionKind.SourceFixAll)) {
276+
const fixAllEdits = await sendCommand<RequestResponseTypes['getFixAllEdits']>('getFixAllEdits')
277+
if (!fixAllEdits) return
278+
const edit = new vscode.WorkspaceEdit()
279+
edit.set(document.uri, tsTextChangesToVcodeTextEdits(document, fixAllEdits))
280+
await vscode.workspace.applyEdit(edit)
281+
}
282+
283+
if (context.triggerKind !== vscode.CodeActionTriggerKind.Invoke) return
280284
const result = await sendTurnIntoArrayRequest(range)
281285
if (!result) return
282286
const { keysCount, totalCount, totalObjectCount } = result

src/util.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
import { offsetPosition } from '@zardoy/vscode-utils/build/position'
2+
import ts from 'typescript'
13
import * as vscode from 'vscode'
24

35
export const tsRangeToVscode = (document: vscode.TextDocument, [start, end]: [number, number]) =>
46
new vscode.Range(document.positionAt(start), document.positionAt(end))
57

68
export const tsRangeToVscodeSelection = (document: vscode.TextDocument, [start, end]: [number, number]) =>
79
new vscode.Selection(document.positionAt(start), document.positionAt(end))
10+
11+
export const tsTextChangesToVcodeTextEdits = (document: vscode.TextDocument, edits: ts.TextChange[]): vscode.TextEdit[] => {
12+
return edits.map(({ span, newText }) => {
13+
const start = document.positionAt(span.start)
14+
return {
15+
range: new vscode.Range(start, offsetPosition(document, start, span.length)),
16+
newText,
17+
}
18+
})
19+
}

typescript/src/codeFixes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type tslib from 'typescript/lib/tsserverlibrary'
21
import addMissingProperties from './codeFixes/addMissingProperties'
32
import { GetConfig } from './types'
43
import { findChildContainingPosition, getIndentFromPos } from './utils'
@@ -17,6 +16,10 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
1716
fix.changes[0]!.textChanges[0]!.span.start = start + length - fixedLength
1817
fix.changes[0]!.textChanges[0]!.span.length = fixedLength
1918
}
19+
// don't let it trigger on ctrl+s https://github.com/microsoft/vscode/blob/e8a3071ea4344d9d48ef8a4df2c097372b0c5161/extensions/typescript-language-features/src/languageFeatures/fixAll.ts#L142
20+
if (fix.fixName === 'fixAwaitInSyncFunction') {
21+
fix.fixName = 'fixedFixAwaitInSyncFunction'
22+
}
2023
return fix
2124
})
2225
// #endregion

typescript/src/completions/markOrRemoveGlobalLibCompletions.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ export default (entries: ts.CompletionEntry[], position: number, languageService
2323
if (action === 'remove') return undefined
2424
return {
2525
...entry,
26-
sourceDisplay: [
27-
{
28-
kind: 'text',
29-
text: libCompletionEnding,
30-
},
31-
],
26+
// TODO for some reason in member compl client (vscode) resolves it to [object Object] with labelDetails
27+
insertText: entry.name,
28+
labelDetails: {
29+
...entry.labelDetails,
30+
description: libCompletionEnding,
31+
},
3232
}
3333
}),
3434
)

typescript/src/completionsAtPosition.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import _ from 'lodash'
2-
import type tslib from 'typescript/lib/tsserverlibrary'
32
import inKeywordCompletions from './inKeywordCompletions'
43
// import * as emmet from '@vscode/emmet-helper'
54
import isInBannedPosition from './completions/isInBannedPosition'
@@ -32,7 +31,7 @@ export const getCompletionsAtPosition = (
3231
c: GetConfig,
3332
languageService: ts.LanguageService,
3433
scriptSnapshot: ts.IScriptSnapshot,
35-
ts: typeof tslib,
34+
formatOptions?: ts.FormatCodeSettings,
3635
):
3736
| {
3837
completions: ts.CompletionInfo
@@ -45,7 +44,7 @@ export const getCompletionsAtPosition = (
4544
const sourceFile = program?.getSourceFile(fileName)
4645
if (!program || !sourceFile) return
4746
if (!scriptSnapshot || isInBannedPosition(position, scriptSnapshot, sourceFile)) return
48-
let prior = languageService.getCompletionsAtPosition(fileName, position, options)
47+
let prior = languageService.getCompletionsAtPosition(fileName, position, options, formatOptions)
4948
const ensurePrior = () => {
5049
if (!prior) prior = { entries: [], isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false }
5150
return true
@@ -235,7 +234,7 @@ export const getCompletionsAtPosition = (
235234
})
236235
}
237236

238-
prior.entries = markOrRemoveGlobalCompletions(prior.entries, position, languageService, c) ?? prior.entries
237+
if (prior.isGlobalCompletion) prior.entries = markOrRemoveGlobalCompletions(prior.entries, position, languageService, c) ?? prior.entries
239238
if (exactNode) prior.entries = filterJsxElements(prior.entries, exactNode, position, languageService, c) ?? prior.entries
240239

241240
if (c('correctSorting.enable')) {

typescript/src/index.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,31 @@ const decorateLanguageService = (info: ts.server.PluginCreateInfo, existingProxy
3939

4040
let prevCompletionsMap: PrevCompletionMap
4141
// eslint-disable-next-line complexity
42-
proxy.getCompletionsAtPosition = (fileName, position, options) => {
42+
proxy.getCompletionsAtPosition = (fileName, position, options, formatOptions) => {
4343
const updateConfigCommand = 'updateConfig'
4444
if (options?.triggerCharacter?.startsWith(updateConfigCommand)) {
4545
_configuration = JSON.parse(options.triggerCharacter.slice(updateConfigCommand.length))
4646
return { entries: [] }
4747
}
4848
const specialCommandResult = options?.triggerCharacter
49-
? handleSpecialCommand(info, fileName, position, options.triggerCharacter as TriggerCharacterCommand, languageService, _configuration)
49+
? handleSpecialCommand(
50+
info,
51+
fileName,
52+
position,
53+
options.triggerCharacter as TriggerCharacterCommand,
54+
languageService,
55+
_configuration,
56+
options,
57+
formatOptions,
58+
)
5059
: undefined
5160
// handled specialCommand request
5261
if (specialCommandResult !== undefined) return specialCommandResult as any
5362
prevCompletionsMap = {}
5463
const scriptSnapshot = info.project.getScriptSnapshot(fileName)
5564
// have no idea in which cases its possible, but we can't work without it
5665
if (!scriptSnapshot) return
57-
const result = getCompletionsAtPosition(fileName, position, options, c, info.languageService, scriptSnapshot, ts)
66+
const result = getCompletionsAtPosition(fileName, position, options, c, info.languageService, scriptSnapshot, formatOptions)
5867
if (!result) return
5968
prevCompletionsMap = result.prevCompletionsMap
6069
return result.completions
@@ -93,6 +102,12 @@ const decorateLanguageService = (info: ts.server.PluginCreateInfo, existingProxy
93102
decorateReferences(proxy, info.languageService, c)
94103
decorateDocumentHighlights(proxy, info.languageService, c)
95104

105+
// todo arg definition
106+
proxy.getCombinedCodeFix = (scope, fixId, formatOptions, preferences) => {
107+
const prior = proxy.getCombinedCodeFix(scope, fixId, formatOptions, preferences)
108+
return prior
109+
}
110+
96111
if (!__WEB__) {
97112
// dedicated syntax server (which is enabled by default), which fires navtree doesn't seem to receive onConfigurationChanged
98113
// so we forced to communicate via fs

typescript/src/ipcTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const triggerCharacterCommands = [
99
'pickAndInsertFunctionArguments',
1010
'getRangeOfSpecialValue',
1111
'turnArrayIntoObject',
12+
'getFixAllEdits',
1213
] as const
1314

1415
export type TriggerCharacterCommand = typeof triggerCharacterCommands[number]
@@ -45,6 +46,7 @@ export type RequestResponseTypes = {
4546
totalObjectCount: number
4647
}
4748
turnArrayIntoObjectEdit: ts.TextChange[]
49+
getFixAllEdits: ts.TextChange[]
4850
}
4951

5052
export type RequestOptionsTypes = {

typescript/src/specialCommands/handle.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export default (
1212
specialCommand: TriggerCharacterCommand,
1313
languageService: ts.LanguageService,
1414
configuration: any,
15+
preferences: ts.UserPreferences,
16+
formatOptions?: ts.FormatCodeSettings,
1517
): void | {
1618
entries: []
1719
typescriptEssentialsResponse: any
@@ -71,6 +73,32 @@ export default (
7173
typescriptEssentialsResponse: postfixesAtPosition(position, fileName, scriptSnapshot, info.languageService),
7274
} as any
7375
}
76+
if (specialCommand === 'getFixAllEdits') {
77+
// code adopted is for asyncInSync fix for now
78+
const interestedCodes = [1308]
79+
const recordedStarts = new Set<number>()
80+
const diagnostics = languageService.getSemanticDiagnostics(fileName)
81+
const edits: ts.TextChange[] = []
82+
for (const { code, start, length } of diagnostics) {
83+
if (!interestedCodes.includes(code)) continue
84+
const fixes = languageService.getCodeFixesAtPosition(fileName, start!, start! + length!, [code], formatOptions ?? {}, preferences)
85+
for (const fix of fixes) {
86+
if (fix.fixName === 'fixAwaitInSyncFunction') {
87+
const textChange = fix.changes[0]!.textChanges[0]!
88+
const { start } = textChange.span
89+
if (!recordedStarts.has(start)) {
90+
recordedStarts.add(start)
91+
edits.push(textChange)
92+
}
93+
break
94+
}
95+
}
96+
}
97+
return {
98+
entries: [],
99+
typescriptEssentialsResponse: edits,
100+
} as any
101+
}
74102
if (specialCommand === 'removeFunctionArgumentsTypesInSelection') {
75103
changeType<RequestOptionsTypes['removeFunctionArgumentsTypesInSelection']>(specialCommandArg)
76104

0 commit comments

Comments
 (0)