From 8c8b6d4dfe16fa58d1152414dc7e21b346ac64f1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 24 Jun 2023 16:08:20 +0300 Subject: [PATCH 1/9] fix(object-literal-completions): don't add comma if its already there fix(object-literal-completions): Handle literal string union complete pattern --- .../src/completions/objectLiteralCompletions.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/typescript/src/completions/objectLiteralCompletions.ts b/typescript/src/completions/objectLiteralCompletions.ts index f0531292..293665ff 100644 --- a/typescript/src/completions/objectLiteralCompletions.ts +++ b/typescript/src/completions/objectLiteralCompletions.ts @@ -66,7 +66,8 @@ export default (prior: ts.CompletionInfo): ts.CompletionEntry[] | void => { const insertSnippetVariant = completingStyleMap.find(([, detector]) => detector(type!, typeChecker))?.[0] ?? fallbackSnippet if (!insertSnippetVariant) continue const [insertSnippetText, insertSnippetPreview] = typeof insertSnippetVariant === 'function' ? insertSnippetVariant() : insertSnippetVariant - const insertText = insertTextAfterEntry(entry, insertSnippetText) + let insertText = insertTextAfterEntry(entry, insertSnippetText) + if (node.getSourceFile().getFullText()[position] === ',') insertText = insertText.slice(0, -1) const index = entries.indexOf(entry) entries.splice(index + (keepOriginal === 'before' ? 1 : 0), keepOriginal === 'remove' ? 1 : 0, { ...entry, @@ -100,10 +101,20 @@ const isEverySubtype = (type: ts.UnionType, predicate: (type: ts.Type) => boolea }) } -const isStringCompletion = (type: ts.Type) => { +const isStringCompletion = (type: ts.Type, checker: ts.TypeChecker) => { if (type.flags & ts.TypeFlags.Undefined) return false if (type.flags & ts.TypeFlags.StringLike) return true - if (type.isUnion()) return isEverySubtype(type, type => isStringCompletion(type)) + if (type.isUnion()) + return isEverySubtype( + type, + type => + isStringCompletion(type, checker) || + // string & {} for string complete + (type.isIntersection() && + type.types.length === 2 && + type.types[0]!.flags & ts.TypeFlags.String && + checker['isEmptyAnonymousObjectType'](type.types[1])), + ) return false } From 537286b9c90cbd15a70a4a0f8da2d1f32a8ae56c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 24 Jun 2023 18:06:29 +0300 Subject: [PATCH 2/9] fix: dont wipe insertText for globalLibCompletions! (when features is on) --- typescript/src/completions/markOrRemoveGlobalLibCompletions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/typescript/src/completions/markOrRemoveGlobalLibCompletions.ts b/typescript/src/completions/markOrRemoveGlobalLibCompletions.ts index 5a8b7c10..0dc04ee0 100644 --- a/typescript/src/completions/markOrRemoveGlobalLibCompletions.ts +++ b/typescript/src/completions/markOrRemoveGlobalLibCompletions.ts @@ -23,8 +23,6 @@ export default (entries: ts.CompletionEntry[], position: number, languageService if (action === 'remove') return undefined return { ...entry, - // TODO for some reason in member compl client (vscode) resolves it to [object Object] with labelDetails - insertText: entry.name, labelDetails: { ...entry.labelDetails, description: `🌐${libCompletionEnding}`, From fd7a6c59c034865ddf04c58f8ff48bb1c2f003e5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 24 Jun 2023 18:08:29 +0300 Subject: [PATCH 3/9] fix: don't complete in invalid positions --- typescript/src/completions/boostNameSuggestions.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/typescript/src/completions/boostNameSuggestions.ts b/typescript/src/completions/boostNameSuggestions.ts index 4c634fda..87aff2d2 100644 --- a/typescript/src/completions/boostNameSuggestions.ts +++ b/typescript/src/completions/boostNameSuggestions.ts @@ -22,7 +22,11 @@ export default ( } const statementsNode = nodeWithStatements(node) || nodeWithStatements(node.parent) // Workaround for current locality bonus & TS 5.1 - if (statementsNode) { + if ( + statementsNode && + // ensure completions are not blocked + entries.length > 0 + ) { const statements = statementsNode.statements as any[] const prevNode = statementsNode === node From 1c6e950a10c961ca9c541e34d616758b9d21f3c1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 14:10:21 +0300 Subject: [PATCH 4/9] feat: e.code key codes hint suggestions --- .vscode/settings.json | 3 + pnpm-lock.yaml | 10 +-- .../src/completions/staticHintSuggestions.ts | 71 +++++++++++++++++++ typescript/src/completionsAtPosition.ts | 6 +- typescript/src/decorateProxy.ts | 6 +- typescript/src/utils.ts | 29 ++++++-- 6 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 typescript/src/completions/staticHintSuggestions.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index dac34fc6..0f6867d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,8 @@ ], "githubPullRequests.ignoredPullRequestBranches": [ "develop" + ], + "cSpell.words": [ + "unpatch" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9fba9cc..ec5470d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -724,7 +724,7 @@ packages: /@types/jsonfile@6.0.1: resolution: {integrity: sha512-SSCc8i9yl6vjgXSyZb0uEodk3UjXuWd55t1D+Ie1zuTx7ml+2AEj0Xyomi3NBz1gCBsZVyWWnXOLXowS1ufhEw==} dependencies: - '@types/node': 16.11.21 + '@types/node': 16.18.3 dev: false /@types/keyv@3.1.4: @@ -5688,7 +5688,7 @@ packages: code-block-writer: 10.1.1 dev: false - /ts-node@10.4.0(@types/node@16.11.21)(typescript@4.2.4): + /ts-node@10.4.0(@types/node@16.18.3)(typescript@4.2.4): resolution: {integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==} hasBin: true peerDependencies: @@ -5707,7 +5707,7 @@ packages: '@tsconfig/node12': 1.0.9 '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 - '@types/node': 16.11.21 + '@types/node': 16.18.3 acorn: 8.7.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -5872,10 +5872,10 @@ packages: resolution: {integrity: sha512-POhWbUNs2oaBti1W9k/JwS+uDsaZD9J/KQiZ/iXRQEOD0lTn9VmshIls9tn+A9X6O+smPjeEz5NEy6WTkCCzrQ==} dependencies: '@types/json-schema': 7.0.9 - '@types/node': 16.11.21 + '@types/node': 16.18.3 glob: 7.2.0 json-stable-stringify: 1.0.1 - ts-node: 10.4.0(@types/node@16.11.21)(typescript@4.2.4) + ts-node: 10.4.0(@types/node@16.18.3)(typescript@4.2.4) typescript: 4.2.4 yargs: 17.3.1 transitivePeerDependencies: diff --git a/typescript/src/completions/staticHintSuggestions.ts b/typescript/src/completions/staticHintSuggestions.ts new file mode 100644 index 00000000..f5552f0f --- /dev/null +++ b/typescript/src/completions/staticHintSuggestions.ts @@ -0,0 +1,71 @@ +import { matchParents, buildNotStrictStringCompletion } from '../utils' +import { sharedCompletionContext } from './sharedContext' + +export default (): ts.CompletionEntry[] | void => { + const { node, program } = sharedCompletionContext + if (!node) return + const comparisonNode = getComparisonNode(node) + if (!comparisonNode) return + if (ts.isPropertyAccessExpression(comparisonNode) && ts.isIdentifier(comparisonNode.name) && comparisonNode.name.text === 'code') { + const typeChecker = program.getTypeChecker() + const symbol = typeChecker.getSymbolAtLocation(comparisonNode.name) + const decl = symbol?.declarations?.[0] + if (!decl) return + if ( + decl.getSourceFile().fileName.endsWith('node_modules/typescript/lib/lib.dom.d.ts') && + matchParents(decl.parent, ['InterfaceDeclaration'])?.name.text === 'KeyboardEvent' + ) { + return allKeyCodes.map(keyCode => buildNotStrictStringCompletion(node as ts.StringLiteralLike, keyCode)) + } + } +} + +// type: https://github.com/zardoy/contro-max/blob/master/src/types/keyCodes.ts +const singleNumber = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +const fNumbers = [...singleNumber.filter(x => x !== 0), 10, 11, 12].map(x => `F${x}`) // actually can go up to 24, but used rarely +const letterKeys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'].map( + x => `Key${x.toUpperCase()}`, +) +const someOtherKeys = [ + 'Space', + 'Esc', + 'Tab', + 'Enter', + 'Equal', + 'Minus', + 'Backslash', + 'Slash', + 'Period', + 'Comma', + 'Capslock', + 'Numlock', + 'PrintScreen', + 'Scrolllock', + 'Pause', + 'Backspace', + 'Delete', + 'Insert', + 'Backquote', + 'BracketLeft', + 'BracketRight', + ...['Up', 'Down', 'Left', 'Right'].map(x => `Arrow${x}`), + 'Home', + 'End', + 'PageUp', + 'PageDown', +] + +const digitKeys = singleNumber.map(x => `Digit${x}`) +const modifierOnlyKeys = ['Meta', 'Control', 'Alt', 'Shift'].flatMap(x => ['', 'Left', 'Right'].map(j => x + j)) + +const numpadKeys = [...singleNumber, 'Divide', 'Multiply', 'Subtract', 'Add', 'Enter', 'Decimal'].map(x => `Numpad${x}`) +const allKeyCodes = [...digitKeys, ...letterKeys, ...fNumbers, ...someOtherKeys, ...modifierOnlyKeys, ...numpadKeys] + +const getComparisonNode = (node: ts.Node) => { + if (!ts.isStringLiteralLike(node)) return + const binaryExpr = matchParents(node.parent, ['BinaryExpression']) + return binaryExpr?.right === node && + [ts.SyntaxKind.EqualsEqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(binaryExpr.operatorToken.kind) + ? binaryExpr.left + : matchParents(node.parent, ['CaseClause', 'CaseBlock', 'SwitchStatement'])?.expression +} diff --git a/typescript/src/completionsAtPosition.ts b/typescript/src/completionsAtPosition.ts index 038e5ad3..0f2a7b4c 100644 --- a/typescript/src/completionsAtPosition.ts +++ b/typescript/src/completionsAtPosition.ts @@ -31,6 +31,7 @@ import { getTupleSignature } from './tupleSignature' import stringTemplateTypeCompletions from './completions/stringTemplateType' import localityBonus from './completions/localityBonus' import functionCompletions from './completions/functionCompletions' +import staticHintSuggestions from './completions/staticHintSuggestions' export type PrevCompletionMap = Record< string, @@ -53,7 +54,7 @@ export type GetCompletionAtPositionReturnType = { completions: ts.CompletionInfo /** Let default getCompletionEntryDetails to know original name or let add documentation from here */ prevCompletionsMap: PrevCompletionMap - prevCompletionsAdittionalData: PrevCompletionsAdditionalData + prevCompletionsAdditionalData: PrevCompletionsAdditionalData } export const getCompletionsAtPosition = ( @@ -289,6 +290,7 @@ export const getCompletionsAtPosition = ( if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap')) prior.entries = localityBonus(prior.entries) ?? prior.entries + prior.entries.push(...(staticHintSuggestions() ?? [])) const processedEntries = new Set() for (const rule of c('replaceSuggestions')) { @@ -410,7 +412,7 @@ export const getCompletionsAtPosition = ( return { completions: prior, prevCompletionsMap, - prevCompletionsAdittionalData: { + prevCompletionsAdditionalData: { enableMethodCompletion: goodPositionForMethodCompletions, completionsSymbolMap, }, diff --git a/typescript/src/decorateProxy.ts b/typescript/src/decorateProxy.ts index 3f83b747..94ed9a6e 100644 --- a/typescript/src/decorateProxy.ts +++ b/typescript/src/decorateProxy.ts @@ -44,7 +44,7 @@ export const decorateLanguageService = ( const proxy = getInitialProxy(languageService, existingProxy) let prevCompletionsMap: PrevCompletionMap - let prevCompletionsAdittionalData: PrevCompletionsAdditionalData + let prevCompletionsAdditionalData: PrevCompletionsAdditionalData proxy.getEditsForFileRename = (oldFilePath, newFilePath, formatOptions, preferences) => { let edits = languageService.getEditsForFileRename(oldFilePath, newFilePath, formatOptions, preferences) @@ -127,11 +127,11 @@ export const decorateLanguageService = ( const result = getCompletionsAtPosition(fileName, position, options, c, languageService, scriptSnapshot, formatOptions, { scriptKind, compilerOptions }) if (!result) return prevCompletionsMap = result.prevCompletionsMap - prevCompletionsAdittionalData = result.prevCompletionsAdittionalData + prevCompletionsAdditionalData = result.prevCompletionsAdditionalData return result.completions } - proxy.getCompletionEntryDetails = (...inputArgs) => completionEntryDetails(inputArgs, languageService, prevCompletionsMap, c, prevCompletionsAdittionalData) + proxy.getCompletionEntryDetails = (...inputArgs) => completionEntryDetails(inputArgs, languageService, prevCompletionsMap, c, prevCompletionsAdditionalData) decorateCodeActions(proxy, languageService, languageServiceHost, c) decorateCodeFixes(proxy, languageService, languageServiceHost, c) diff --git a/typescript/src/utils.ts b/typescript/src/utils.ts index f9726006..f3c6130a 100644 --- a/typescript/src/utils.ts +++ b/typescript/src/utils.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-require-imports */ import { Except, SetOptional } from 'type-fest' import * as semver from 'semver' +import type { MatchParentsType } from './utilTypes' export function findChildContainingPosition(typescript: typeof ts, sourceFile: ts.SourceFile, position: number): ts.Node | undefined { function find(node: ts.Node): ts.Node | undefined { @@ -164,12 +165,13 @@ export const isWeb = () => { } // spec isnt strict as well -export const notStrictStringCompletion = (entry: ts.CompletionEntry): ts.CompletionEntry => ({ - ...entry, - // todo - name: `◯${entry.name}`, - insertText: entry.insertText ?? entry.name, -}) +export const buildNotStrictStringCompletion = (node: ts.StringLiteralLike, text: string): ts.CompletionEntry => + buildStringCompletion(node, { + // ...entry, + sortText: '07', + name: `💡${text}`, + insertText: text, + }) export function addObjectMethodResultInterceptors>( object: T, @@ -295,3 +297,18 @@ export const patchMethod = (obj: T, method: K, overriden: export const insertTextAfterEntry = (entryOrName: ts.CompletionEntry | string, appendText: string) => (typeof entryOrName === 'string' ? entryOrName : entryOrName.name).replace(/\$/g, '\\$') + appendText + +export const matchParents: MatchParentsType = (node, treeToCompare) => { + let first = true + for (const toCompare of treeToCompare) { + if (!first) { + node = node?.parent + first = false + } + if (!node) return + if (!(ts[`is${toCompare}` as keyof typeof ts] as (node) => boolean)(node)) { + return + } + } + return node as any +} From eee48c3192606e5f94e6bfe6d5650c0e62aee291 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 14:56:47 +0300 Subject: [PATCH 5/9] feat(new-command): replace global typescript with local version, so you can update TS globally for bug fixes and so on --- package.json | 8 ++--- src/extension.ts | 55 ++--------------------------- src/nonTsCommands.ts | 79 ++++++++++++++++++++++++++++++++++++++++++ src/specialCommands.ts | 10 +++--- 4 files changed, 90 insertions(+), 62 deletions(-) create mode 100644 src/nonTsCommands.ts diff --git a/package.json b/package.json index f696f6ac..7785dedf 100644 --- a/package.json +++ b/package.json @@ -55,13 +55,13 @@ "command": "copyFullType", "title": "Copy Full Type" }, - { - "command": "pasteCodeWithImports", - "title": "Paste Code with Imports" - }, { "command": "disableAllOptionalFeatures", "title": "Disable All Optional Features" + }, + { + "command": "replaceGlobalTypescriptWithLocalVersion", + "title": "Replace Global Typescript with Local Version" } ], "keybindings": [ diff --git a/src/extension.ts b/src/extension.ts index 473a1597..b002aed3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import vueVolarSupport from './vueVolarSupport' import moreCompletions from './moreCompletions' import { mergeSettingsFromScopes } from './mergeSettings' import codeActionProvider from './codeActionProvider' +import nonTsCommands from './nonTsCommands' let isActivated = false // let erroredStatusBarItem: vscode.StatusBarItem | undefined @@ -91,7 +92,7 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted } export const activate = async () => { - registerDisableOptionalFeaturesCommand() + nonTsCommands() migrateSettings() const possiblyActivateTsPlugin = async () => { @@ -140,55 +141,3 @@ export const activate = async () => { }) } } - -const registerDisableOptionalFeaturesCommand = () => { - registerExtensionCommand('disableAllOptionalFeatures', async () => { - const config = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, null) - const toDisable: Array<[keyof Settings, any]> = [] - for (const optionalExperience of optionalExperiences) { - const desiredKey = Array.isArray(optionalExperience) ? optionalExperience[0] : optionalExperience - const desiredValue = Array.isArray(optionalExperience) ? optionalExperience[1] : false - if (config.get(desiredKey) !== desiredValue) toDisable.push([desiredKey, desiredValue]) - } - - const action = await vscode.window.showInformationMessage( - `${toDisable.length} features are going to be disabled`, - { detail: '', modal: true }, - 'Write to settings NOW', - 'Copy settings', - ) - if (!action) return - switch (action) { - case 'Write to settings NOW': { - for (const [key, value] of toDisable) { - void config.update(key, value, vscode.ConfigurationTarget.Global) - } - - break - } - - case 'Copy settings': { - await vscode.env.clipboard.writeText(JSON.stringify(Object.fromEntries(toDisable), undefined, 4)) - break - } - } - }) -} - -/** Experiences that are enabled out of the box */ -const optionalExperiences: Array | [keyof Settings, any]> = [ - 'enableMethodSnippets', - 'removeUselessFunctionProps.enable', - 'patchToString.enable', - ['suggestions.keywordsInsertText', 'none'], - 'highlightNonFunctionMethods.enable', - 'markTsCodeActions.enable', - ['markTsCodeFixes.character', ''], - 'removeCodeFixes.enable', - 'removeDefinitionFromReferences', - 'removeImportsFromReferences', - 'miscDefinitionImprovement', - 'improveJsxCompletions', - 'objectLiteralCompletions.moreVariants', - 'codeActions.extractTypeInferName', -] diff --git a/src/nonTsCommands.ts b/src/nonTsCommands.ts new file mode 100644 index 00000000..f2001430 --- /dev/null +++ b/src/nonTsCommands.ts @@ -0,0 +1,79 @@ +import * as vscode from 'vscode' +import { ConditionalPick } from 'type-fest' +import { registerExtensionCommand, Settings } from 'vscode-framework' +import { getCurrentWorkspaceRoot } from '@zardoy/vscode-utils/build/fs' +import { Utils } from 'vscode-uri' +import { showQuickPick } from '@zardoy/vscode-utils/build/quickPick' + +// these commands doesn't require TS to be available + +export default () => { + registerExtensionCommand('disableAllOptionalFeatures', async () => { + const config = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, null) + const toDisable: Array<[keyof Settings, any]> = [] + for (const optionalExperience of optionalExperiences) { + const desiredKey = Array.isArray(optionalExperience) ? optionalExperience[0] : optionalExperience + const desiredValue = Array.isArray(optionalExperience) ? optionalExperience[1] : false + if (config.get(desiredKey) !== desiredValue) toDisable.push([desiredKey, desiredValue]) + } + + const action = await vscode.window.showInformationMessage( + `${toDisable.length} features are going to be disabled`, + { detail: '', modal: true }, + 'Write to settings NOW', + 'Copy settings', + ) + if (!action) return + switch (action) { + case 'Write to settings NOW': { + for (const [key, value] of toDisable) { + void config.update(key, value, vscode.ConfigurationTarget.Global) + } + + break + } + + case 'Copy settings': { + await vscode.env.clipboard.writeText(JSON.stringify(Object.fromEntries(toDisable), undefined, 4)) + break + } + } + }) + + registerExtensionCommand('replaceGlobalTypescriptWithLocalVersion', async () => { + const root = getCurrentWorkspaceRoot() + const localTypeScript = Utils.joinPath(root.uri, 'node_modules/typescript') + const globalTypeScript = Utils.joinPath(vscode.Uri.file(vscode.env.appRoot), 'extensions/node_modules/typescript') + const { version: localVersion } = await vscode.workspace.fs + .readFile(Utils.joinPath(localTypeScript, 'package.json')) + .then(result => JSON.parse(result.toString())) + const { version: globalVersion } = await vscode.workspace.fs + .readFile(Utils.joinPath(globalTypeScript, 'package.json')) + .then(result => JSON.parse(result.toString())) + const result = await showQuickPick([`Replace global TS ${globalVersion} with local ${localVersion}`].map(x => ({ value: x, label: x }))) + if (!result) return + const paths = ['package.json', 'lib'] + for (const path of paths) { + // eslint-disable-next-line no-await-in-loop + await vscode.workspace.fs.copy(Utils.joinPath(localTypeScript, path), Utils.joinPath(globalTypeScript, path), { overwrite: true }) + } + }) +} + +/** Experiences that are enabled out of the box */ +const optionalExperiences: Array | [keyof Settings, any]> = [ + 'enableMethodSnippets', + 'removeUselessFunctionProps.enable', + 'patchToString.enable', + ['suggestions.keywordsInsertText', 'none'], + 'highlightNonFunctionMethods.enable', + 'markTsCodeActions.enable', + ['markTsCodeFixes.character', ''], + 'removeCodeFixes.enable', + 'removeDefinitionFromReferences', + 'removeImportsFromReferences', + 'miscDefinitionImprovement', + 'improveJsxCompletions', + 'objectLiteralCompletions.moreVariants', + 'codeActions.extractTypeInferName', +] diff --git a/src/specialCommands.ts b/src/specialCommands.ts index fba14794..f3cf33fe 100644 --- a/src/specialCommands.ts +++ b/src/specialCommands.ts @@ -289,9 +289,9 @@ export default () => { await vscode.env.clipboard.writeText(text) }) - registerExtensionCommand('pasteCodeWithImports', async () => { - const clipboard = await vscode.env.clipboard.readText() - const lines = clipboard.split('\n') - const lastImportLineIndex = lines.findIndex(line => line !== 'import') - }) + // registerExtensionCommand('pasteCodeWithImports', async () => { + // const clipboard = await vscode.env.clipboard.readText() + // const lines = clipboard.split('\n') + // const lastImportLineIndex = lines.findIndex(line => line !== 'import') + // }) } From cc4990580762adb87a165f7387e5c4d873578f4e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 15:06:00 +0300 Subject: [PATCH 6/9] feat: update locality bonus! Now variables defined before have more priority, added a setting so you can return old behavior, which was also improved. --- src/configurationType.ts | 5 +++++ typescript/src/completions/localityBonus.ts | 21 ++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/configurationType.ts b/src/configurationType.ts index 8656079d..c551d503 100644 --- a/src/configurationType.ts +++ b/src/configurationType.ts @@ -127,6 +127,11 @@ export type Configuration = { * @default false */ 'suggestions.localityBonus': boolean + /** + * position = cursor position + * @default prefer-before-position + */ + 'suggestions.localityBonusMode': 'prefer-before-position' | 'nearest-to-position' // TODO! corrent watching! /** * Wether to enable snippets for array methods like `items.map(item => )` diff --git a/typescript/src/completions/localityBonus.ts b/typescript/src/completions/localityBonus.ts index cec7e5c1..f741f53e 100644 --- a/typescript/src/completions/localityBonus.ts +++ b/typescript/src/completions/localityBonus.ts @@ -1,24 +1,27 @@ import { sharedCompletionContext } from './sharedContext' export default (entries: ts.CompletionEntry[]) => { - const { node, sourceFile, c } = sharedCompletionContext + const { node, sourceFile, c, position } = sharedCompletionContext if (!c('suggestions.localityBonus')) return - const getScore = entry => { - // TODO once TS is updated resolve - // eslint-disable-next-line prefer-destructuring - const symbol: ts.Symbol | undefined = entry['symbol'] + if (!node) return + const LOWEST_SCORE = node.getSourceFile().getFullText().length + const getScore = (entry: ts.CompletionEntry) => { + const { symbol } = entry if (!symbol) return const { valueDeclaration = symbol.declarations?.[0] } = symbol if (!valueDeclaration) return - if (valueDeclaration.getSourceFile().fileName !== sourceFile.fileName) return -1 - return valueDeclaration.pos + if (valueDeclaration.getSourceFile().fileName !== sourceFile.fileName) return LOWEST_SCORE + const completionPos = valueDeclaration.pos + valueDeclaration.getLeadingTriviaWidth() + if (c('suggestions.localityBonusMode') === 'nearest-to-position') { + return Math.abs(completionPos - position) + } + return completionPos < position ? -position - completionPos : completionPos - position } - if (!node) return return [...entries].sort((a, b) => { const aScore = getScore(a) const bScore = getScore(b) if (aScore === undefined || bScore === undefined) return 0 - return bScore - aScore + return aScore - bScore }) } From 1d439f2f75e848c1c78330109b46064957239b8d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 15:07:31 +0300 Subject: [PATCH 7/9] fix: remove workaround to place previous variable completion at top, update TS (now there is a command for this) or/and enable locality bonus! --- .../src/completions/boostNameSuggestions.ts | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/typescript/src/completions/boostNameSuggestions.ts b/typescript/src/completions/boostNameSuggestions.ts index 87aff2d2..2ce3a56d 100644 --- a/typescript/src/completions/boostNameSuggestions.ts +++ b/typescript/src/completions/boostNameSuggestions.ts @@ -3,8 +3,8 @@ import { getCannotFindCodes } from '../utils/cannotFindCodes' const cannotFindCodes = getCannotFindCodes({ includeFromLib: true }) -// 1. add suggestions for unresolved indentifiers in code -// 2. boost identifer or type name suggestion +// 1. add suggestions for unresolved identifiers in code +// 2. boost identifier or type name suggestion export default ( entries: ts.CompletionEntry[], position: number, @@ -17,35 +17,7 @@ export default ( const fileText = sourceFile.getFullText() const fileTextBeforePos = fileText.slice(0, position) const beforeConstNodeOffset = fileTextBeforePos.match(/(?:const|let) ([\w\d]*)$/i)?.[1] - const nodeWithStatements = node => { - return node && 'statements' in node && Array.isArray(node.statements) ? node : undefined - } - const statementsNode = nodeWithStatements(node) || nodeWithStatements(node.parent) - // Workaround for current locality bonus & TS 5.1 - if ( - statementsNode && - // ensure completions are not blocked - entries.length > 0 - ) { - const statements = statementsNode.statements as any[] - const prevNode = - statementsNode === node - ? [...statements].reverse().find((statement: ts.Node) => statement.pos + statement.getLeadingTriviaWidth() < position) - : statements[statements.indexOf(node) - 1] - if (prevNode && ts.isVariableStatement(prevNode) && prevNode.declarationList.declarations.length === 1) { - const { name } = prevNode.declarationList.declarations[0]! - if (ts.isIdentifier(name)) { - const kind: ts.ScriptElementKind = - prevNode.declarationList.flags & ts.NodeFlags.Const ? ts.ScriptElementKind.constElement : ts.ScriptElementKind.letElement - entries = boostOrAddSuggestions(entries, [ - { - name: name.text, - kind, - }, - ]) - } - } - } + /** false - pick all identifiers after cursor * node - pick identifiers that within node */ let filterBlock: undefined | false | ts.Node From 2e468232b293b73e478564795e748bacda319da1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 15:11:45 +0300 Subject: [PATCH 8/9] fix some spelling mistakes in settings --- src/configurationType.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/configurationType.ts b/src/configurationType.ts index c551d503..0b3f053d 100644 --- a/src/configurationType.ts +++ b/src/configurationType.ts @@ -15,7 +15,7 @@ type ReplaceRule = { fileNamePattern?: string languageMode?: keyof typeof ScriptKind } - /** by default only first entry is proccessed */ + /** by default only first entry is processed */ processMany?: boolean delete?: boolean /** @@ -25,7 +25,7 @@ type ReplaceRule = { patch?: Partial<{ name: string kind: keyof typeof ScriptElementKind - /** Might be useless when `correntSorting.enable` is true */ + /** Might be useless when `correctSorting.enable` is true */ sortText: string insertText: string | true /** Wether insertText differs from completion name */ @@ -36,7 +36,7 @@ type ReplaceRule = { description?: string } }> - /** Works only with `correntSorting.enable` set to true (default) */ + /** Works only with `correctSorting.enable` set to true (default) */ // movePos?: number /** When specified, `movePos` is ignored */ // TODO! @@ -86,7 +86,7 @@ export type Configuration = { 'patchToString.enable': boolean /** * Format of this setting is very close to `jsxCompletionsMap` setting: - * `path#symbol` (exact) or `path/*#symbol` (`#symbol` part can be ommited) + * `path#symbol` (exact) or `path/*#symbol` (`#symbol` part can be omitted) * * Note: Please use `javascript`/`typescript.preferences.autoImportFileExcludePatterns` when possible, to achieve better performance! * @@ -132,7 +132,7 @@ export type Configuration = { * @default prefer-before-position */ 'suggestions.localityBonusMode': 'prefer-before-position' | 'nearest-to-position' - // TODO! corrent watching! + // TODO! correct watching! /** * Wether to enable snippets for array methods like `items.map(item => )` * @default false @@ -150,7 +150,7 @@ export type Configuration = { */ 'arrayMethodsSnippets.addOuterTabStop': boolean /** - * If set to `false` and singular item name can't be inffered, feature will be disabled + * If set to `false` and singular item name can't be inferred, feature will be disabled * @default item */ 'arrayMethodsSnippets.defaultItemName': string | false @@ -280,12 +280,12 @@ export type Configuration = { */ 'jsxPseudoEmmet.tags': { [tag: string]: true | string } /** - * Exclude lowercase / incorrent suggestions + * Exclude lowercase / incorrect suggestions * @default true */ 'jsxImproveElementsSuggestions.enabled': boolean /** - * Recommended to enable to experience less uneeded suggestions unless you are using JSX Elements declared in namespaces + * Recommended to enable to experience less useless suggestions unless you are using JSX Elements declared in namespaces * @default false */ 'jsxImproveElementsSuggestions.filterNamespaces': boolean @@ -343,7 +343,7 @@ export type Configuration = { miscDefinitionImprovement: boolean // todo change setting format to: vue.* /** - * Removes definiion suggestion from vue `components` options. + * Removes definition suggestion from vue `components` options. * Might be useful with [Vetur-extended goToDefinition](https://github.com/zardoy/vetur-extended/blob/main/src/gotoDefinition.ts) for components as a replacement for (https://github.com/vuejs/language-tools/issues/1245) * @default false */ @@ -436,7 +436,7 @@ export type Configuration = { */ 'disableMethodSnippets.jsxAttributes': boolean /** - * Support `@ts-diagnostic-disable` top-level comment for disabling spefici semantic diagnostics + * Support `@ts-diagnostic-disable` top-level comment for disabling specific semantic diagnostics * Example: `// @ts-diagnostic-disable * Advanced usage only! Enable in `.vscode/settings.json` for projects that need this * Since its changes only IDE experience, but not tsc @@ -470,7 +470,7 @@ export type Configuration = { */ switchExcludeCoveredCases: boolean /** - * Make completions case-sensetive (see https://github.com/microsoft/TypeScript/issues/46622) + * Make completions case-sensitive (see https://github.com/microsoft/TypeScript/issues/46622) * Might be enabled by default in future. Experimental as for now compares only start of completions. * Might require completion retrigger if was triggered by not quick suggestions. * @default false @@ -478,7 +478,7 @@ export type Configuration = { caseSensitiveCompletions: boolean /** * Might be useful to enable for a moment. Note, that you can bind shortcuts within VSCode to quickly toggle settings like this - * Also experimental and wasnt tested in all cases + * Also experimental and wasn't tested in all cases * Like described in `caseSensitiveCompletions` might require completion retrigger * @default false */ @@ -487,6 +487,7 @@ export type Configuration = { * Disable useless highlighting, * @default disable */ + // todo fix spelling disableUselessHighlighting: 'disable' | 'inJsxArttributeStrings' | 'inAllStrings' /** * Improve JSX attribute completions: @@ -497,7 +498,7 @@ export type Configuration = { improveJsxCompletions: boolean /** * Replace JSX completions by map with `tagName#attribute` pattern as keys - * `tagName` can be ommited, but not `attribute` for now + * `tagName` can be omitted, but not `attribute` for now * Example usages: * - `#className`: `insertText: "={classNames$1}"` * - `button#type`: `insertText: "='button'"` @@ -560,7 +561,7 @@ export type Configuration = { */ 'experiments.excludeNonJsxCompletions': boolean /** - * Wether to change funcntion completions to function kind + * Wether to change function completions to function kind * @default false */ 'experiments.changeKindToFunction': boolean @@ -605,7 +606,7 @@ export type Configuration = { */ useDefaultImport?: boolean /** - * Set to `false` if module is acessible from global variable + * Set to `false` if the module is accessible from the global variable * For now not supported in add all missing imports code action * @default true */ addImport?: boolean @@ -637,7 +638,7 @@ export type Configuration = { } } -// scrapped using search editor. config: caseInsesetive, context lines: 0, regex: const fix\w+ = "[^ ]+" +// scrapped using search editor. config: caseInsensitive, context lines: 0, regex: const fix\w+ = "[^ ]+" type FixId = | 'addConvertToUnknownForNonOverlappingTypes' | 'addMissingAsync' From d6d1ff49d67fa35b9c7c1aba0d5eeefcaaeaf7cf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Jul 2023 15:18:39 +0300 Subject: [PATCH 9/9] add missing file --- typescript/src/utilTypes.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 typescript/src/utilTypes.ts diff --git a/typescript/src/utilTypes.ts b/typescript/src/utilTypes.ts new file mode 100644 index 00000000..b9dae8f8 --- /dev/null +++ b/typescript/src/utilTypes.ts @@ -0,0 +1,14 @@ +import { ConditionalPick } from 'type-fest' + +type Tail = T extends readonly [...any[], infer U] ? U : never +type GetIs = T extends (elem: any) => elem is infer T ? T : never + +type TSNodeIs = ConditionalPick node is ts.Node> +type Comparisons = { + [T in keyof TSNodeIs as T extends `is${infer U}` ? /* Uncapitalize */ U : never]: GetIs<(typeof ts)[T & keyof typeof ts]> +} + +export type MatchParentsType = ( + node: ts.Node | undefined, + treeToCompare: T, +) => Comparisons[Tail & keyof Comparisons] | undefined