Skip to content

Commit

Permalink
Merge pull request #210 from zardoy/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
zardoy authored May 24, 2024
2 parents 0ae5b20 + 16743dd commit 0aa9607
Show file tree
Hide file tree
Showing 23 changed files with 212 additions and 40 deletions.
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,
Expand Down
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
Expand Up @@ -685,6 +685,11 @@ export type Configuration = {
*/
declareMissingPropertyQuickfixOtherFiles: boolean
/**
* @recommended {".svg": {
* "importPath": "$path?react",
* "prefix": "Svg",
* "nameCasing": "pascal"
* },
* @default {}
*/
filesAutoImport: {
Expand All @@ -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+ = "[^ ]+"
Expand Down
4 changes: 4 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
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
Expand Up @@ -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,
Expand All @@ -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,
}
}

Expand Down
3 changes: 2 additions & 1 deletion typescript/src/codeActions/getCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,7 +23,7 @@ const codeActions: CodeAction[] = [
renameAllParametersToNameFromType,
fixClosingTagName,
]
const extendedCodeActions: ExtendedCodeAction[] = [declareMissingProperties]
const extendedCodeActions: ExtendedCodeAction[] = [declareMissingProperties, declareMissingAttributes]

type SimplifiedRefactorInfo =
| {
Expand Down
10 changes: 5 additions & 5 deletions typescript/src/codeFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)) {
Expand All @@ -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)
Expand All @@ -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])
Expand Down
3 changes: 2 additions & 1 deletion typescript/src/completionEntryDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -54,6 +54,7 @@ export default function completionEntryDetails(
source,
preferences,
data,
...args,
)
if (detailPrepend) {
prior ??= {
Expand Down
3 changes: 2 additions & 1 deletion typescript/src/completions/filesAutoImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions typescript/src/completionsAtPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -94,6 +95,8 @@ export const getCompletionsAtPosition = (
includeSymbol: true,
},
formatOptions,
//@ts-expect-error
...args,
)
} finally {
unpatch?.()
Expand Down
4 changes: 2 additions & 2 deletions typescript/src/decorateEditsForFileRename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(/\..+/, '')
Expand Down
4 changes: 2 additions & 2 deletions typescript/src/decorateLinkedEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 0aa9607

Please sign in to comment.