Skip to content

Commit f922848

Browse files
authored
Merge pull request #79 from zardoy/develop
2 parents 4f40a80 + 79a336c commit f922848

18 files changed

+601
-129
lines changed

README.MD

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,52 @@ 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+
```
181+
182+
## Auto Imports
183+
184+
With this plugin you have total (almost) control over auto imports that appear in completions, quick fixes and import all quick fix. Some examples of what you can do:
185+
186+
- I never want to see `join` suggestion from `path/win32` module (it's a Node.js [module](https://nodejs.org/dist/latest-v18.x/docs/api/path.html#pathwin32), defined via module augmentation)
187+
- I never want to see any suggestions from `path/*` modules
188+
- I always want `join` to be imported from `path/posix` module on window (also would appear first in completions & single quick fix)
189+
- I always want `useState` to be imported from local files first
190+
191+
Some settings examples:
192+
193+
<!-- just duplicated from configurationType -->
194+
195+
```jsonc
196+
"suggestions.ignoreAutoImports": [
197+
"path", // ignore path, but not path/posix or path/win32 modules
198+
"path/*", // ignore path, path/posix and path/win32
199+
"path/*#join", // ignore path, path/posix and path/win32, but only join symbol
200+
"path/*#join,resolve", // ignore path, path/posix and path/win32, but only join and resolve symbol
201+
],
202+
"autoImport.changeSorting": {
203+
"join": ["path/posix"], // other suggestions will be below
204+
"resolve": ["*", "path/posix"], // make `path/posix` appear below any other suggestions
205+
"useState": ["."], // `.` (dot) is reserved for local suggestions, which makes them appear above other
206+
},
207+
// for some even more specific cases?
208+
// "autoImport.alwaysIgnoreInImportAll": ["lodash"] // exclude all possible imports from import all request
209+
```
210+
211+
> Note: changeSorting might not preserve sorting of other existing suggestions which not defined by rules, there is WIP
212+
> Also I'm thinking of making it learn and syncing of most-used imports automatically
168213
169214
## Special Commands List
170215

src/configurationType.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,42 @@ export type Configuration = {
4545
* */
4646
'removeUselessFunctionProps.enable': boolean
4747
/**
48+
* Of course it makes no sense to use `remove`, but `mark` might be really useful
4849
* @default disable
4950
*/
50-
'removeOrMarkGlobalCompletions.action': 'disable' | 'mark' | 'remove'
51+
'removeOrMarkGlobalLibCompletions.action': 'disable' | 'mark' | 'remove'
5152
/**
5253
* Useful for Number types.
5354
* Patch `toString()`: Removes arg tabstop
5455
* @default true
5556
*/
5657
'patchToString.enable': boolean
5758
/**
59+
* Format of this setting is very close to `jsxCompletionsMap` setting:
60+
* `path#symbol` (exact) or `path/*#symbol` (`#symbol` part can be ommited)
61+
*
5862
* Note: Please use `javascript`/`typescript.preferences.autoImportFileExcludePatterns` when possible, to achieve better performance!
63+
*
5964
* e.g. instead of declaring `@mui/icons-material` here, declare `node_modules/@mui/icons-material` in aforementioned setting.
6065
*
6166
* And only use this, if auto-imports coming not from physical files (e.g. some modules node imports)
67+
*
68+
* Examples:
69+
* - `path` - ignore path, but not path/posix or path/win32 modules
70+
* - `path/*` - ignore path, path/posix and path/win32
71+
* - `path/*#join` - ignore path, path/posix and path/win32, but only join symbol
72+
* - `path/*#join,resolve` - ignore path, path/posix and path/win32, but only join and resolve symbol
73+
*
74+
* - jquery/* - ignore absolutely all auto imports from jquery, even if it was declared virtually (declare module)
6275
* @default []
6376
*/
64-
'suggestions.banAutoImportPackages': string[]
77+
'suggestions.ignoreAutoImports': string[]
78+
/**
79+
* Disable it only if it causes problems / crashes with TypeScript, which of course should never happen
80+
* But it wasn't tested on very old versions
81+
* @default false
82+
*/
83+
// 'advanced.disableAutoImportCodeFixPatching': boolean
6584
/**
6685
* What insert text to use for keywords (e.g. `return`)
6786
* @default space
@@ -364,4 +383,23 @@ export type Configuration = {
364383
* @default false
365384
*/
366385
'experiments.excludeNonJsxCompletions': boolean
386+
/**
387+
* Map *symbol - array of modules* to change sorting of imports - first available takes precedence in auto import code fixes (+ import all action)
388+
*
389+
* Examples:
390+
* - `join`: ['path/posix'] // other suggestions will be below
391+
* - `resolve`: ['*', 'path/posix'] // make `path/posix` appear below any other suggestions
392+
* - `useEventListener`: ['.'] // `.` (dot) is reserved for local suggestions, which makes them appear above other
393+
* @default {}
394+
*/
395+
'autoImport.changeSorting': { [pathAndOrSymbol: string]: string[] }
396+
/**
397+
* Advanced. Use `suggestions.ignoreAutoImports` setting if possible.
398+
*
399+
* Packages to ignore in import all fix.
400+
*
401+
* TODO syntaxes /* and module#symbol unsupported (easy)
402+
* @default []
403+
*/
404+
'autoImport.alwaysIgnoreInImportAll': string[]
367405
}

src/migrateSettings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export default () => {
1717
mustBePrimitive: true,
1818
},
1919
},
20+
{
21+
rename: {
22+
old: 'suggestions.banAutoImportPackages',
23+
new: 'suggestions.ignoreAutoImports',
24+
mustBePrimitive: false,
25+
},
26+
},
2027
],
2128
process.env.IDS_PREFIX!,
2229
)

src/specialCommands.ts

Lines changed: 17 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,28 @@ 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')) {
272+
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)
279281
return
282+
}
283+
284+
if (context.triggerKind !== vscode.CodeActionTriggerKind.Invoke) return
280285
const result = await sendTurnIntoArrayRequest(range)
281286
if (!result) return
282287
const { keysCount, totalCount, totalObjectCount } = result

src/util.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import * as vscode from 'vscode'
2+
import { offsetPosition } from '@zardoy/vscode-utils/build/position'
23

34
export const tsRangeToVscode = (document: vscode.TextDocument, [start, end]: [number, number]) =>
45
new vscode.Range(document.positionAt(start), document.positionAt(end))
56

67
export const tsRangeToVscodeSelection = (document: vscode.TextDocument, [start, end]: [number, number]) =>
78
new vscode.Selection(document.positionAt(start), document.positionAt(end))
9+
10+
export const tsTextChangesToVcodeTextEdits = (document: vscode.TextDocument, edits: Array<import('typescript').TextChange>): vscode.TextEdit[] =>
11+
edits.map(({ span, newText }) => {
12+
const start = document.positionAt(span.start)
13+
return {
14+
range: new vscode.Range(start, offsetPosition(document, start, span.length)),
15+
newText,
16+
}
17+
})

typescript/src/adjustAutoImports.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { GetConfig } from './types'
2+
import { addObjectMethodResultInterceptors, findChildContainingExactPosition } from './utils'
3+
4+
let currentSymbolName: string | undefined
5+
6+
interface ParsedIgnoreSetting {
7+
module: string
8+
symbols: string[]
9+
isAnySymbol: boolean
10+
moduleCompare: 'startsWith' | 'strict'
11+
}
12+
13+
// will be removed once I'm sure performance can't be improved
14+
const initIgnoreAutoImport = () => {
15+
addObjectMethodResultInterceptors(tsFull, {
16+
// todo
17+
createPackageJsonImportFilter(res, fromFile) {
18+
return {
19+
...res,
20+
allowsImportingAmbientModule(moduleSymbol, moduleSpecifierResolutionHost) {
21+
return true
22+
// return isModuleShouldBeIgnored(moduleSymbol.name.slice(1, -1), '')
23+
},
24+
// allowsImportingSourceFile(sourceFile, moduleSpecifierResolutionHost) {
25+
// return false
26+
// },
27+
allowsImportingSpecifier(moduleSpecifier) {
28+
const result = res.allowsImportingSpecifier(moduleSpecifier)
29+
if (!result) return false
30+
return false
31+
},
32+
}
33+
},
34+
})
35+
// addObjectMethodResultInterceptors(tsFull.codefix, {
36+
// getAllFixes(res) {
37+
// return res
38+
// },
39+
// })
40+
}
41+
42+
export const getIgnoreAutoImportSetting = (c: GetConfig) => {
43+
return c('suggestions.ignoreAutoImports').map((spec): ParsedIgnoreSetting => {
44+
const hashIndex = spec.indexOf('#')
45+
let module = hashIndex === -1 ? spec : spec.slice(0, hashIndex)
46+
const moduleCompare = module.endsWith('/*') ? 'startsWith' : 'strict'
47+
if (moduleCompare === 'startsWith') {
48+
module = module.slice(0, -'/*'.length)
49+
}
50+
if (hashIndex === -1) {
51+
return {
52+
module,
53+
symbols: [],
54+
isAnySymbol: true,
55+
moduleCompare,
56+
}
57+
}
58+
const symbolsString = spec.slice(hashIndex + 1)
59+
// * (glob asterisk) is reserved for future ussage
60+
const isAnySymbol = symbolsString === '*'
61+
return {
62+
module,
63+
symbols: isAnySymbol ? [] : symbolsString.split(','),
64+
isAnySymbol,
65+
moduleCompare,
66+
}
67+
})
68+
}
69+
70+
export const isAutoImportEntryShouldBeIgnored = (ignoreAutoImportsSetting: ParsedIgnoreSetting[], targetModule: string, symbol: string) => {
71+
for (const { module, moduleCompare, isAnySymbol, symbols } of ignoreAutoImportsSetting) {
72+
const isIgnoreModule = moduleCompare === 'startsWith' ? targetModule.startsWith(module) : targetModule === module
73+
if (!isIgnoreModule) continue
74+
if (isAnySymbol) return true
75+
if (!symbols.includes(symbol)) continue
76+
return true
77+
}
78+
return false
79+
}
80+
81+
export const shouldChangeSortingOfAutoImport = (symbolName: string, c: GetConfig) => {
82+
const arr = c('autoImport.changeSorting')[symbolName]
83+
return arr && arr.length > 0
84+
}
85+
86+
export const changeSortingOfAutoImport = (c: GetConfig, symbolName: string): ((module: string) => number) => {
87+
const arr = c('autoImport.changeSorting')[symbolName]
88+
if (!arr || !arr.length) return () => 0
89+
const maxIndex = arr.length
90+
return module => {
91+
let actualIndex = arr.findIndex(x => {
92+
return x.endsWith('/*') ? module.startsWith(x.slice(0, -'/*'.length)) : module === x
93+
})
94+
// . - index, don't treat some node_modules-ignored modules as local such as `.vite`
95+
const isLocal = module.startsWith('./') || module.startsWith('../') || module === '.'
96+
if (actualIndex === -1) actualIndex = arr.findIndex(x => (isLocal ? x === '.' : x === '*'))
97+
return actualIndex === -1 ? maxIndex : actualIndex
98+
}
99+
}

0 commit comments

Comments
 (0)