Skip to content

Last release #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "ts-essential-plugins",
"displayName": "TypeScript Essential Plugins",
"description": "50+ features: TS extension for professionals",
"version": "0.0.0-dev",
"license": "MIT",
"web": true,
23 changes: 23 additions & 0 deletions src/autoCompletionsTrigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as vscode from 'vscode'
import { defaultLanguageSupersets } from '@zardoy/vscode-utils/build/langs'
import { getExtensionSetting } from 'vscode-framework'
import { sendCommand } from './sendCommand'

const jsxAttributesAutoTrigger = () => {
vscode.workspace.onDidChangeTextDocument(async ({ contentChanges, document, reason }) => {
const editor = vscode.window.activeTextEditor
if (document !== editor?.document || contentChanges.length === 0) return
if (contentChanges[0]!.text !== ' ') return
if (![...defaultLanguageSupersets.react, 'javascript'].includes(document.languageId)) return
if (!getExtensionSetting('completionsAutoTrigger.jsx')) return
const path = await sendCommand('getNodePath', { document, position: editor.selection.active })
if (!path) return
if (['JsxSelfClosingElement', 'JsxOpeningElement'].includes(path.at(-1)?.kindName ?? '')) {
await vscode.commands.executeCommand('editor.action.triggerSuggest')
}
})
}

export default () => {
jsxAttributesAutoTrigger()
}
13 changes: 13 additions & 0 deletions src/configurationType.ts
Original file line number Diff line number Diff line change
@@ -685,6 +685,11 @@ export type Configuration = {
*/
declareMissingPropertyQuickfixOtherFiles: boolean
/**
* @recommended {".svg": {
* "importPath": "$path?react",
* "prefix": "Svg",
* "nameCasing": "pascal"
* },
* @default {}
*/
filesAutoImport: {
@@ -708,6 +713,14 @@ export type Configuration = {
iconPost?: string
}
}
/**
* @default true
*/
'completionsAutoTrigger.jsx': boolean
/**
* @default false
*/
'inlayHints.missingJsxAttributes.enabled': boolean
}

// scrapped using search editor. config: caseInsensitive, context lines: 0, regex: const fix\w+ = "[^ ]+"
4 changes: 4 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ import moreCompletions from './moreCompletions'
import { mergeSettingsFromScopes } from './mergeSettings'
import codeActionProvider from './codeActionProvider'
import nonTsCommands from './nonTsCommands'
import inlayHints from './inlayHints'
import autoCompletionsTrigger from './autoCompletionsTrigger'

let isActivated = false
// let erroredStatusBarItem: vscode.StatusBarItem | undefined
@@ -96,6 +98,8 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted

figIntegration()
vueVolarSupport()
inlayHints()
autoCompletionsTrigger()

if (process.env.PLATFORM === 'node' && process.env.NODE_ENV === 'development') {
require('./autoPluginReload').default()
57 changes: 57 additions & 0 deletions src/inlayHints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as vscode from 'vscode'
import { watchExtensionSetting } from '@zardoy/vscode-utils/build/settings'
import { getExtensionSetting, registerActiveDevelopmentCommand } from 'vscode-framework'

// todo respect enabled setting, deactivate
export default () => {
const provider = new (class implements vscode.InlayHintsProvider {
eventEmitter = new vscode.EventEmitter<void>()
onDidChangeInlayHints = this.eventEmitter.event
provideInlayHints(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): vscode.ProviderResult<vscode.InlayHint[]> {
const diagnostics = vscode.languages.getDiagnostics(document.uri)
const jsxMissingAttributesErrors = diagnostics.filter(({ code, source }) => (code === 2740 || code === 2739) && source === 'ts')
return jsxMissingAttributesErrors
.flatMap(({ range, message }) => {
const regex = /: (?<prop>[\w, ]+)(?:, and (?<more>\d+) more)?\.?$/
const match = regex.exec(message)
if (!match) return null as never
const props = match.groups!.prop!.split(', ')
const { more } = match.groups!
let text = ` ${props.map(prop => `${prop}!`).join(', ')}`
if (more) text += `, and ${more} more`
return {
kind: vscode.InlayHintKind.Type,
label: text,
tooltip: `Inlay hint: Missing attributes`,
position: range.end,
paddingLeft: true,
} satisfies vscode.InlayHint
// return [...props, ...(more ? [more] : [])].map((prop) => ({
// kind: vscode.InlayHintKind.Type,
// label: prop,
// tooltip: 'Missing attribute',
// position:
// }))
})
.filter(Boolean)
}
})()
let disposables = [] as vscode.Disposable[]

const manageEnablement = () => {
if (getExtensionSetting('inlayHints.missingJsxAttributes.enabled')) {
vscode.languages.registerInlayHintsProvider('typescriptreact,javascript,javascriptreact'.split(','), provider)
vscode.languages.onDidChangeDiagnostics(e => {
for (const uri of e.uris) {
if (uri === vscode.window.activeTextEditor?.document.uri) provider.eventEmitter.fire()
}
})
} else {
for (const d of disposables) d.dispose()
disposables = []
}
}

manageEnablement()
watchExtensionSetting('inlayHints.missingJsxAttributes.enabled', manageEnablement)
}
44 changes: 44 additions & 0 deletions typescript/src/codeActions/extended/declareMissingAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ExtendedCodeAction } from '../getCodeActions'

const errorCodes = [
// ts.Diagnostics.Property_0_does_not_exist_on_type_1.code,
// ts.Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
// ts.Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2.code,
// ts.Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2.code,
// ts.Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code,
// // ts.Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code,
// // ts.Diagnostics.Cannot_find_name_0.code,
2339, 2551, 2741, 2739, 2740 /* 2345, 2304, */,
]

export default {
codes: errorCodes,
kind: 'quickfix',
title: 'Declare missing attributes',
tryToApply({ sourceFile, node, c, languageService, position, formatOptions, range }) {
// todo maybe cache from prev request?
if (!node) return
const codeFixes = languageService.getCodeFixesAtPosition(
sourceFile.fileName,
node.getStart(),
range?.end ?? node.getStart(),
errorCodes,
formatOptions ?? {},
{},
)
const fix = codeFixes.find(codeFix => codeFix.fixName === 'fixMissingAttributes')
if (fix && fix.changes[0]?.textChanges.length === 1) {
const changes = fix.changes[0]!.textChanges
let i = 1
return {
snippetEdits: [
{
newText: changes[0]!.newText.replaceAll('$', '\\$').replaceAll('={undefined}', () => `={$${i++}}`),
span: fix.changes[0]!.textChanges[0]!.span,
},
],
}
}
return
},
} as ExtendedCodeAction
21 changes: 15 additions & 6 deletions typescript/src/codeActions/functionExtractors.ts
Original file line number Diff line number Diff line change
@@ -100,14 +100,17 @@ export const handleFunctionRefactorEdits = (
const oldFunctionText = functionChange.newText
const sourceFile = languageService.getProgram()!.getSourceFile(fileName)!
if (actionName.endsWith('_jsx')) {
// refactor.extract.jsx implementation
const lines = oldFunctionText.trimStart().split('\n')
const oldFunctionSignature = lines[0]!
const componentName = tsFull.getUniqueName('ExtractedComponent', sourceFile as unknown as FullSourceFile)
const newFunctionSignature = changeArgumentsToDestructured(oldFunctionSignature, formatOptions, sourceFile, componentName)

const insertChange = textChanges.at(-2)!
let args = insertChange.newText.slice(1, -2)
args = args.slice(args.indexOf('(') + 1)
const args = insertChange.newText.slice(insertChange.newText.indexOf('(') + 1, insertChange.newText.lastIndexOf(')'))

const newFunctionSignature = changeArgumentsToDestructured(oldFunctionSignature, formatOptions, sourceFile, componentName).replace('{}: {}', '')

const oldSpan = sourceFile.text.slice(0, functionChange.span.start).length

const fileEdits = [
{
fileName,
@@ -130,11 +133,17 @@ export const handleFunctionRefactorEdits = (
],
},
]
const diff = fileEdits[0]!.textChanges.slice(0, -1).reduce((diff, { newText, span }) => {
const oldText = sourceFile.text.slice(span.start, span.start + span.length)
const newSpan = newText.length
const oldSpan = oldText.length
diff += newSpan - oldSpan
return diff
}, 0)
return {
edits: fileEdits,
renameFilename,
renameLocation: insertChange.span.start + 1,
// renameLocation: tsFull.getRenameLocation(fileEdits, fileName, componentName, /*preferLastLocation*/ false),
renameLocation: functionChange.span.start + diff,
}
}

3 changes: 2 additions & 1 deletion typescript/src/codeActions/getCodeActions.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import { renameParameterToNameFromType, renameAllParametersToNameFromType } from
import addDestructure_1 from './custom/addDestructure/addDestructure'
import fromDestructure_1 from './custom/fromDestructure/fromDestructure'
import fixClosingTagName from './custom/fixClosingTagName'
import declareMissingAttributes from './extended/declareMissingAttributes'

const codeActions: CodeAction[] = [
addDestructure_1,
@@ -22,7 +23,7 @@ const codeActions: CodeAction[] = [
renameAllParametersToNameFromType,
fixClosingTagName,
]
const extendedCodeActions: ExtendedCodeAction[] = [declareMissingProperties]
const extendedCodeActions: ExtendedCodeAction[] = [declareMissingProperties, declareMissingAttributes]

type SimplifiedRefactorInfo =
| {
10 changes: 5 additions & 5 deletions typescript/src/codeFixes.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import { findChildContainingPosition, getCancellationToken, getIndentFromPos, is
import namespaceAutoImports from './namespaceAutoImports'

export default (proxy: ts.LanguageService, languageService: ts.LanguageService, languageServiceHost: ts.LanguageServiceHost, c: GetConfig) => {
proxy.getCodeFixesAtPosition = (fileName, start, end, errorCodes, formatOptions, preferences) => {
proxy.getCodeFixesAtPosition = (fileName, start, end, errorCodes, formatOptions, preferences, ...args) => {
const sourceFile = languageService.getProgram()!.getSourceFile(fileName)!
const node = findChildContainingPosition(ts, sourceFile, start)

@@ -72,7 +72,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
},
)
toUnpatch.push(unpatch)
prior = languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)
prior = languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences, ...args)
prior = [...addNamespaceImports, ...prior]
prior = _.sortBy(prior, ({ fixName }) => {
if (fixName.startsWith(importFixName)) {
@@ -82,7 +82,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
})
prior = prior.filter(x => x.fixName !== 'IGNORE')
} catch (err) {
prior = languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)
prior = languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences, ...args)
setTimeout(() => {
// make sure we still get code fixes, but error is still getting reported
console.error(err)
@@ -103,14 +103,14 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
// #endregion

const semanticDiagnostics = languageService.getSemanticDiagnostics(fileName)
const syntacicDiagnostics = languageService.getSyntacticDiagnostics(fileName)
const syntacticDiagnostics = languageService.getSyntacticDiagnostics(fileName)

// https://github.com/Microsoft/TypeScript/blob/v4.5.5/src/compiler/diagnosticMessages.json#L458
const findDiagnosticByCode = (codes: number[]) => {
const errorCode = codes.find(code => errorCodes.includes(code))
if (!errorCode) return
const diagnosticPredicate = ({ code, start: localStart }) => code === errorCode && localStart === start
return syntacicDiagnostics.find(diagnosticPredicate) || semanticDiagnostics.find(diagnosticPredicate)
return syntacticDiagnostics.find(diagnosticPredicate) || semanticDiagnostics.find(diagnosticPredicate)
}

const wrapBlockDiagnostics = findDiagnosticByCode([1156, 1157])
3 changes: 2 additions & 1 deletion typescript/src/completionEntryDetails.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ export default function completionEntryDetails(
c: GetConfig,
{ enableMethodCompletion, completionsSymbolMap }: PrevCompletionsAdditionalData,
): ts.CompletionEntryDetails | undefined {
const [fileName, position, entryName, formatOptions, source, preferences, data] = inputArgs
const [fileName, position, entryName, formatOptions, source, preferences, data, ...args] = inputArgs
lastResolvedCompletion.value = { name: entryName, range: prevCompletionsMap[entryName]?.range }
const program = languageService.getProgram()
const sourceFile = program?.getSourceFile(fileName)
@@ -54,6 +54,7 @@ export default function completionEntryDetails(
source,
preferences,
data,
...args,
)
if (detailPrepend) {
prior ??= {
3 changes: 2 additions & 1 deletion typescript/src/completions/filesAutoImport.ts
Original file line number Diff line number Diff line change
@@ -60,7 +60,8 @@ export default () => {
const files = collected.filter(f => f.endsWith(ext))
for (const file of files) {
const fullPath = nodeModules.path.join(root, file)
const relativeToFile = nodeModules.path.relative(nodeModules.path.dirname(sourceFile.fileName), fullPath).replaceAll('\\', '/')
let relativeToFile = nodeModules.path.relative(nodeModules.path.dirname(sourceFile.fileName), fullPath).replaceAll('\\', '/')
if (!relativeToFile.startsWith('.')) relativeToFile = `./${relativeToFile}`
const lastModified = nodeModules.fs.statSync(fullPath).mtime
const lastModifiedFormatted = timeDifference(Date.now(), lastModified.getTime())
const importPath = (item.importPath ?? '$path').replaceAll('$path', relativeToFile)
3 changes: 3 additions & 0 deletions typescript/src/completionsAtPosition.ts
Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ export const getCompletionsAtPosition = (
scriptSnapshot: ts.IScriptSnapshot,
formatOptions: ts.FormatCodeSettings | undefined,
additionalData: { scriptKind: ts.ScriptKind; compilerOptions: ts.CompilerOptions },
...args: any[]
): GetCompletionAtPositionReturnType | undefined => {
const prevCompletionsMap: PrevCompletionMap = {}
const program = languageService.getProgram()
@@ -94,6 +95,8 @@ export const getCompletionsAtPosition = (
includeSymbol: true,
},
formatOptions,
//@ts-expect-error
...args,
)
} finally {
unpatch?.()
4 changes: 2 additions & 2 deletions typescript/src/decorateEditsForFileRename.ts
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ import { GetConfig } from './types'
import { approveCast, findChildContainingExactPosition } from './utils'

export default (proxy: ts.LanguageService, languageService: ts.LanguageService, c: GetConfig) => {
proxy.getEditsForFileRename = (oldFilePath, newFilePath, formatOptions, preferences) => {
let edits = languageService.getEditsForFileRename(oldFilePath, newFilePath, formatOptions, preferences)
proxy.getEditsForFileRename = (oldFilePath, newFilePath, formatOptions, preferences, ...args) => {
let edits = languageService.getEditsForFileRename(oldFilePath, newFilePath, formatOptions, preferences, ...args)
if (c('renameImportNameOfFileRename')) {
const predictedNameFromPath = (p: string) => {
const input = p.split(/[/\\]/g).pop()!.replace(/\..+/, '')
4 changes: 2 additions & 2 deletions typescript/src/decorateLinkedEditing.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
result: ts.LinkedEditingInfo
}
| undefined
proxy.getLinkedEditingRangeAtPosition = (fileName, position) => {
proxy.getLinkedEditingRangeAtPosition = (fileName, position, ...props) => {
const scriptSnapshot = languageServiceHost.getScriptSnapshot(fileName)!
const fileContent = scriptSnapshot.getText(0, scriptSnapshot.getLength())
const lastChar = fileContent[position - 1]
@@ -37,7 +37,7 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService,
}
lastLinkedEditingRangeRequest = undefined

const prior = languageService.getLinkedEditingRangeAtPosition(fileName, position)
const prior = languageService.getLinkedEditingRangeAtPosition(fileName, position, ...props)
if (!prior) return
lastLinkedEditingRangeRequest = {
pos: position,
Loading