From 8c07026227af4d74a7c7ebde42ff4e83a7abe669 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 20 Jun 2023 19:31:30 +0300 Subject: [PATCH 1/5] fix: fix plugin code actions compatibility with locale --- typescript/src/codeActions/decorateProxy.ts | 2 +- .../src/codeActions/functionExtractors.ts | 22 ++++++++++--------- typescript/src/completionsAtPosition.ts | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/typescript/src/codeActions/decorateProxy.ts b/typescript/src/codeActions/decorateProxy.ts index 49a354a5..f44e15c8 100644 --- a/typescript/src/codeActions/decorateProxy.ts +++ b/typescript/src/codeActions/decorateProxy.ts @@ -20,7 +20,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService, const program = languageService.getProgram()! const sourceFile = program.getSourceFile(fileName)! processApplicableRefactors( - prior.find(r => r.description === 'Extract function'), + prior.find(r => r.name === 'Extract Symbol' && r.actions.some(action => action.kind?.startsWith('refactor.extract.function')))?.actions, c, positionOrRange, sourceFile, diff --git a/typescript/src/codeActions/functionExtractors.ts b/typescript/src/codeActions/functionExtractors.ts index 853ef841..52fe2671 100644 --- a/typescript/src/codeActions/functionExtractors.ts +++ b/typescript/src/codeActions/functionExtractors.ts @@ -9,16 +9,16 @@ import { } from '../utils' export const processApplicableRefactors = ( - refactor: ts.ApplicableRefactorInfo | undefined, + refactorActions: ts.RefactorActionInfo[] | undefined, c: GetConfig, posOrRange: number | ts.TextRange, sourceFile: ts.SourceFile, ) => { - if (!refactor) return - const functionExtractors = refactor?.actions.filter(({ notApplicableReason }) => !notApplicableReason) + if (!refactorActions) return + const functionExtractors = refactorActions.filter(({ notApplicableReason }) => !notApplicableReason) if (functionExtractors?.length) { const kind = functionExtractors[0]!.kind! - const blockScopeRefactor = functionExtractors.find(e => e.description.startsWith('Extract to inner function in')) + const blockScopeRefactor = functionExtractors.find(e => e.description.includes('inner function')) const addArrowCodeActions: ts.RefactorActionInfo[] = [] if (blockScopeRefactor) { addArrowCodeActions.push({ @@ -28,9 +28,7 @@ export const processApplicableRefactors = ( }) } let addExtractToJsxRefactor = false - const globalScopeRefactor = functionExtractors.find(e => - ['Extract to function in global scope', 'Extract to function in module scope'].includes(e.description), - ) + const globalScopeRefactor = functionExtractors.at(-1) if (globalScopeRefactor) { addArrowCodeActions.push({ description: 'Extract to arrow function in global scope above', @@ -42,8 +40,12 @@ export const processApplicableRefactors = ( } if (addExtractToJsxRefactor) { - refactor.actions = refactor.actions.filter(action => !action.name.startsWith('function_scope')) - refactor.actions.push({ + for (const refactorAction of refactorActions) { + if (refactorAction.name.startsWith('function_scope')) { + refactorAction.notApplicableReason = 'JSX Element Selected. Use Extract to JSX component' + } + } + refactorActions.push({ description: 'Extract to JSX component', kind: 'refactor.extract.jsx', name: `${globalScopeRefactor!.name}_jsx`, @@ -51,7 +53,7 @@ export const processApplicableRefactors = ( return } - refactor.actions.push(...addArrowCodeActions) + refactorActions.push(...addArrowCodeActions) } } diff --git a/typescript/src/completionsAtPosition.ts b/typescript/src/completionsAtPosition.ts index 18c8b7d0..038e5ad3 100644 --- a/typescript/src/completionsAtPosition.ts +++ b/typescript/src/completionsAtPosition.ts @@ -15,7 +15,7 @@ import prepareTextForEmmet from './specialCommands/prepareTextForEmmet' import switchCaseExcludeCovered from './completions/switchCaseExcludeCovered' import additionalTypesSuggestions from './completions/additionalTypesSuggestions' import boostKeywordSuggestions from './completions/boostKeywordSuggestions' -import boostTextSuggestions from './completions/boostNameSuggestions' +import boostNameSuggestions from './completions/boostNameSuggestions' import keywordsSpace from './completions/keywordsSpace' import jsdocDefault from './completions/jsdocDefault' import defaultHelpers from './completions/defaultHelpers' @@ -143,7 +143,7 @@ export const getCompletionsAtPosition = ( } if (leftNode) { - const newEntries = boostTextSuggestions(prior?.entries ?? [], position, sourceFile, leftNode, languageService) + const newEntries = boostNameSuggestions(prior?.entries ?? [], position, sourceFile, leftNode, languageService) if (newEntries?.length && ensurePrior() && prior) prior.entries = newEntries } From ee007446cf3b56edc4c5f6d6acb57b1a97ffe098 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 20 Jun 2023 19:40:59 +0300 Subject: [PATCH 2/5] fix: fix outline for localized typescript (use logic from completions) fixes #135 --- typescript/src/codeActions/functionExtractors.ts | 1 - typescript/src/definitions.ts | 2 +- typescript/src/getPatchedNavTree.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/typescript/src/codeActions/functionExtractors.ts b/typescript/src/codeActions/functionExtractors.ts index 52fe2671..b5c63bed 100644 --- a/typescript/src/codeActions/functionExtractors.ts +++ b/typescript/src/codeActions/functionExtractors.ts @@ -1,4 +1,3 @@ -import { equals } from 'rambda' import { GetConfig } from '../types' import { createDummySourceFile, diff --git a/typescript/src/definitions.ts b/typescript/src/definitions.ts index efd8c934..b0fefffd 100644 --- a/typescript/src/definitions.ts +++ b/typescript/src/definitions.ts @@ -108,7 +108,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService, } } } - return + return prior } if (__WEB__) { diff --git a/typescript/src/getPatchedNavTree.ts b/typescript/src/getPatchedNavTree.ts index 1aa87c19..ebdf496f 100644 --- a/typescript/src/getPatchedNavTree.ts +++ b/typescript/src/getPatchedNavTree.ts @@ -128,9 +128,9 @@ const getPatchedNavModule = (additionalFeatures: AdditionalFeatures): { getNavig }) const notFoundVariables = new Set() const cannotFindCodes = getCannotFindCodes({ includeFromLib: false }) - for (const { code, messageText } of languageService.getSemanticDiagnostics('main.ts')) { + for (const { code, start, length } of languageService.getSemanticDiagnostics('main.ts')) { if (!cannotFindCodes.includes(code)) continue - const notFoundName = (typeof messageText === 'object' ? messageText.messageText : messageText).match(/^Cannot find name '(.+?)'./)?.[1] + const notFoundName = moduleString.slice(start, start! + length!) if (!notFoundName) continue notFoundVariables.add(notFoundName) } From 4f383627201f92362c95948303be3d4eb4030486 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 20 Jun 2023 19:41:51 +0300 Subject: [PATCH 3/5] highlight: make code more obvious --- typescript/src/documentHighlights.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/typescript/src/documentHighlights.ts b/typescript/src/documentHighlights.ts index d7b7790f..33a91586 100644 --- a/typescript/src/documentHighlights.ts +++ b/typescript/src/documentHighlights.ts @@ -5,14 +5,10 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService, proxy.getDocumentHighlights = (fileName, position, filesToSearch) => { const prior = languageService.getDocumentHighlights(fileName, position, filesToSearch) if (!prior) return - if (prior.length !== 1) return prior + if (prior.length !== 1 || c('disableUselessHighlighting') === 'disable') return prior const node = findChildContainingPosition(ts, languageService.getProgram()!.getSourceFile(fileName)!, position) if (!node) return prior - if ( - c('disableUselessHighlighting') !== 'disable' && - ts.isStringLiteralLike(node) && - (c('disableUselessHighlighting') === 'inAllStrings' || ts.isJsxAttribute(node.parent)) - ) { + if (ts.isStringLiteralLike(node) && (c('disableUselessHighlighting') === 'inAllStrings' || ts.isJsxAttribute(node.parent))) { return } return prior From 8033fa2ce1cd2f4fec34ea2c24ccdd24f734deef Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 21 Jun 2023 18:44:43 +0300 Subject: [PATCH 4/5] feat: add a command to disable all optional features after install (finally!) --- README.MD | 4 ++- package.json | 6 ++++- src/configurationType.ts | 31 ++++++++++++----------- src/extension.ts | 53 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/README.MD b/README.MD index 1105ed01..d6dec33e 100644 --- a/README.MD +++ b/README.MD @@ -16,7 +16,9 @@ TOC: - [Contributed Code Actions](#contributed-code-actions) - [Even Even More](#even-even-more) -> *Note* Visit website for list of recommended settings: +> *Note*: You can disable all optional features with `> Disable All Optional Features` setting right after install. +> +> *Note*: Visit website for list of recommended settings: ## Top Features diff --git a/package.json b/package.json index 78dad739..f696f6ac 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,10 @@ { "command": "pasteCodeWithImports", "title": "Paste Code with Imports" + }, + { + "command": "disableAllOptionalFeatures", + "title": "Disable All Optional Features" } ], "keybindings": [ @@ -90,7 +94,7 @@ "keywords": [ "ts", "javascript", - "plugin", + "pro", "webstorm", "typescript hero" ], diff --git a/src/configurationType.ts b/src/configurationType.ts index d9b20790..8656079d 100644 --- a/src/configurationType.ts +++ b/src/configurationType.ts @@ -118,6 +118,7 @@ export type Configuration = { /** * Will be `format-short` by default in future as super useful! * Requires TypeScript 5.0+ + * @recommended * @default disable */ 'suggestions.displayImportedInfo': 'disable' | 'short-format' | 'long-format' @@ -160,14 +161,15 @@ export type Configuration = { * */ 'correctSorting.enable': boolean /** - * Try to restore suggestion sorting after `.` + * Try to restore properties (not variables!) sorting as in source * Experimental and most probably will be changed in future + * @recommended * @default false */ fixSuggestionsSorting: boolean - // TODO + // TODO-low /** - * Mark QuickFixes & refactorings with 🔵 + * Mark refactorings with 🔵 * @default true */ 'markTsCodeActions.enable': boolean @@ -205,6 +207,11 @@ export type Configuration = { * @default true * */ 'removeCodeFixes.enable': boolean + /** + * @default ["fixMissingFunctionDeclaration"] + * @uniqueItems true + * */ + 'removeCodeFixes.codefixes': FixId[] /** * Also rename name of default or namespace import on refactor caused by file move / rename * Probably will be enabled by default in future @@ -228,13 +235,8 @@ export type Configuration = { */ workspaceSymbolSearchExcludePatterns: string[] /** - * @default ["fixMissingFunctionDeclaration"] - * @uniqueItems true - * */ - 'removeCodeFixes.codefixes': FixId[] - /** - * Use full-blown emmet in jsx/tsx files! - * Requires `jsxPseudoEmmet.enabled` to be disabled and `emmet.excludeLanguages` to have `javascriptreact` and `typescriptreact` + * Use strict & precise emmet in jsx/tsx files! Doesn't annoy you everywhere! + * Requires `jsxPseudoEmmet.enabled` to be disabled and `emmet.excludeLanguages` to have `javascriptreact` / `typescriptreact` * @default true * */ 'jsxEmmet.enable': boolean @@ -443,11 +445,11 @@ export type Configuration = { */ // completionHelpers: boolean /** - * Extend TypeScript outline! - * Extend outline with: + * Extend TypeScript outline with: * - JSX Elements * - Type Alias Declarations * Should be stable! + * @recommended * @default false */ patchOutline: boolean @@ -458,6 +460,7 @@ export type Configuration = { 'outline.arraysTuplesNumberedItems': boolean /** * Exclude covered strings/enum cases in switch in completions + * @deprecated Will be removed in next release * @default true */ switchExcludeCoveredCases: boolean @@ -520,12 +523,12 @@ export type Configuration = { */ 'figIntegration.enableWhenStartsWith': string[] /** - * Propose additional completions in object. Just like `typescript.suggest.objectLiteralMethodSnippets.enabled`, but also for string, arrays and objects + * Propose additional completions in object. Just like `typescript.suggest.objectLiteralMethodSnippets.enabled`, but also for strings, arrays and objects * @default true */ 'objectLiteralCompletions.moreVariants': boolean /** - * When `moreVariants` enabled, always add as fallback variant if other variant can't be derived + * When `moreVariants` is enabled, always add fallback variant (`: `) if other variant can't be derived * @default false */ 'objectLiteralCompletions.fallbackVariant': boolean diff --git a/src/extension.ts b/src/extension.ts index 2980de89..2ec661bb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-require-imports */ import * as vscode from 'vscode' import { defaultJsSupersetLangs } from '@zardoy/vscode-utils/build/langs' -import { extensionCtx, getExtensionSetting, getExtensionSettingId } from 'vscode-framework' +import { Settings, extensionCtx, getExtensionSetting, getExtensionSettingId, registerExtensionCommand } from 'vscode-framework' import { pickObj } from '@zardoy/utils' import { watchExtensionSettings } from '@zardoy/vscode-utils/build/settings' import webImports from './webImports' @@ -16,6 +16,7 @@ import vueVolarSupport from './vueVolarSupport' import moreCompletions from './moreCompletions' import { mergeSettingsFromScopes } from './mergeSettings' import codeActionProvider from './codeActionProvider' +import { ConditionalPick } from 'type-fest' let isActivated = false // let erroredStatusBarItem: vscode.StatusBarItem | undefined @@ -90,6 +91,7 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted } export const activate = async () => { + registerDisableOptionalFeaturesCommand() migrateSettings() const possiblyActivateTsPlugin = async () => { @@ -138,3 +140,52 @@ export const activate = async () => { }) } } + +const registerDisableOptionalFeaturesCommand = () => { + registerExtensionCommand('disableAllOptionalFeatures', async () => { + const config = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, null) + const toDisable: [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) { + config.update(key, value, vscode.ConfigurationTarget.Global) + } + break + } + case 'Copy settings': { + vscode.env.clipboard.writeText(JSON.stringify(Object.fromEntries(toDisable), undefined, 4)) + break + } + } + }) +} + +/** Experiences that are enabled out of the box */ +const optionalExperiences: (keyof ConditionalPick | [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', +] From f76e81b53d853b0c4c853e44e22ef952941c5be0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 21 Jun 2023 19:51:33 +0300 Subject: [PATCH 5/5] feat: boost name suggestion after its declaration --- src/extension.ts | 13 +++++---- .../src/completions/boostNameSuggestions.ts | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 2ec661bb..473a1597 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,6 +4,7 @@ import { defaultJsSupersetLangs } from '@zardoy/vscode-utils/build/langs' import { Settings, extensionCtx, getExtensionSetting, getExtensionSettingId, registerExtensionCommand } from 'vscode-framework' import { pickObj } from '@zardoy/utils' import { watchExtensionSettings } from '@zardoy/vscode-utils/build/settings' +import { ConditionalPick } from 'type-fest' import webImports from './webImports' import { sendCommand } from './sendCommand' import { registerEmmet } from './emmet' @@ -16,7 +17,6 @@ import vueVolarSupport from './vueVolarSupport' import moreCompletions from './moreCompletions' import { mergeSettingsFromScopes } from './mergeSettings' import codeActionProvider from './codeActionProvider' -import { ConditionalPick } from 'type-fest' let isActivated = false // let erroredStatusBarItem: vscode.StatusBarItem | undefined @@ -144,12 +144,13 @@ export const activate = async () => { const registerDisableOptionalFeaturesCommand = () => { registerExtensionCommand('disableAllOptionalFeatures', async () => { const config = vscode.workspace.getConfiguration(process.env.IDS_PREFIX, null) - const toDisable: [keyof Settings, any][] = [] + 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 }, @@ -160,12 +161,14 @@ const registerDisableOptionalFeaturesCommand = () => { switch (action) { case 'Write to settings NOW': { for (const [key, value] of toDisable) { - config.update(key, value, vscode.ConfigurationTarget.Global) + void config.update(key, value, vscode.ConfigurationTarget.Global) } + break } + case 'Copy settings': { - vscode.env.clipboard.writeText(JSON.stringify(Object.fromEntries(toDisable), undefined, 4)) + await vscode.env.clipboard.writeText(JSON.stringify(Object.fromEntries(toDisable), undefined, 4)) break } } @@ -173,7 +176,7 @@ const registerDisableOptionalFeaturesCommand = () => { } /** Experiences that are enabled out of the box */ -const optionalExperiences: (keyof ConditionalPick | [keyof Settings, any])[] = [ +const optionalExperiences: Array | [keyof Settings, any]> = [ 'enableMethodSnippets', 'removeUselessFunctionProps.enable', 'patchToString.enable', diff --git a/typescript/src/completions/boostNameSuggestions.ts b/typescript/src/completions/boostNameSuggestions.ts index a547626e..4c634fda 100644 --- a/typescript/src/completions/boostNameSuggestions.ts +++ b/typescript/src/completions/boostNameSuggestions.ts @@ -17,12 +17,37 @@ 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) { + 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 if (beforeConstNodeOffset !== undefined) { const node = findChildContainingPosition(ts, sourceFile, position - beforeConstNodeOffset.length - 2) - if (!node || !ts.isVariableDeclarationList(node)) return + if (!node || !ts.isVariableDeclarationList(node)) return entries filterBlock = false } else if (ts.isIdentifier(node) && node.parent?.parent) { // node > parent1 > parent2 @@ -44,7 +69,7 @@ export default ( } } - if (filterBlock === undefined) return + if (filterBlock === undefined) return entries const semanticDiagnostics = languageService.getSemanticDiagnostics(sourceFile.fileName) const notFoundIdentifiers = semanticDiagnostics