Skip to content

Commit 7079496

Browse files
authored
refactor(code-actions): unify quick fix providers with strategy pattern (#46)
* refactor(code-actions): unify quick fix providers with strategy pattern * refactor: simplify test
1 parent 1dd6915 commit 7079496

14 files changed

Lines changed: 156 additions & 180 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"cSpell.words": [
33
"npmx"
4+
],
5+
"typescript.preferences.autoImportSpecifierExcludeRegexes": [
6+
"tests/__mocks__"
47
]
58
}

src/composables/active-extractor.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import type { Extractor } from '#types/extractor'
2-
import { PACKAGE_JSON_BASENAME, PNPM_WORKSPACE_BASENAME } from '#constants'
32
import { computed, useActiveTextEditor } from 'reactive-vscode'
43
import { languages } from 'vscode'
5-
import { PackageJsonExtractor } from '../extractors/package-json'
6-
import { PnpmWorkspaceYamlExtractor } from '../extractors/pnpm-workspace-yaml'
7-
8-
export const extractorEntries = [
9-
{ pattern: `**/${PACKAGE_JSON_BASENAME}`, extractor: new PackageJsonExtractor() },
10-
{ pattern: `**/${PNPM_WORKSPACE_BASENAME}`, extractor: new PnpmWorkspaceYamlExtractor() },
11-
]
4+
import { extractorEntries } from '../extractors'
125

136
export function useActiveExtractor() {
147
const activeEditor = useActiveTextEditor()

src/constants.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,3 @@ export const NPMX_DEV = 'https://npmx.dev'
1010
export const NPMX_DEV_API = `${NPMX_DEV}/api`
1111

1212
export const SPACER = ' '
13-
14-
export const UPGRADE_MESSAGE_PREFIX = 'New version available: '

src/extractors/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { PACKAGE_JSON_BASENAME, PNPM_WORKSPACE_BASENAME } from '#constants'
2+
import { PackageJsonExtractor } from './package-json'
3+
import { PnpmWorkspaceYamlExtractor } from './pnpm-workspace-yaml'
4+
5+
export const extractorEntries = [
6+
{ pattern: `**/${PACKAGE_JSON_BASENAME}`, extractor: new PackageJsonExtractor() },
7+
{ pattern: `**/${PNPM_WORKSPACE_BASENAME}`, extractor: new PnpmWorkspaceYamlExtractor() },
8+
]

src/index.ts

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { extractorEntries } from '#composables/active-extractor'
21
import { VERSION_TRIGGER_CHARACTERS } from '#constants'
32
import { defineExtension, useCommands, watchEffect } from 'reactive-vscode'
4-
import { CodeActionKind, Disposable, languages } from 'vscode'
3+
import { Disposable, languages } from 'vscode'
54
import { openFileInNpmx } from './commands/open-file-in-npmx'
65
import { openInBrowser } from './commands/open-in-browser'
6+
import { extractorEntries } from './extractors'
77
import { commands, displayName, version } from './generated-meta'
8-
import { UpgradeProvider } from './providers/code-actions/upgrade'
9-
import { VulnerabilityCodeActionProvider } from './providers/code-actions/vulnerability'
8+
import { useCodeActions } from './providers/code-actions'
109
import { VersionCompletionItemProvider } from './providers/completion-item/version'
1110
import { useDiagnostics } from './providers/diagnostics'
1211
import { NpmxHoverProvider } from './providers/hover/npmx'
@@ -41,34 +40,10 @@ export const { activate, deactivate } = defineExtension(() => {
4140
onCleanup(() => Disposable.from(...disposables).dispose())
4241
})
4342

44-
watchEffect((onCleanup) => {
45-
if (!config.diagnostics.upgrade)
46-
return
47-
48-
const provider = new UpgradeProvider()
49-
const options = { providedCodeActionKinds: [CodeActionKind.QuickFix] }
50-
const disposables = extractorEntries.map(({ pattern }) =>
51-
languages.registerCodeActionsProvider({ pattern }, provider, options),
52-
)
53-
54-
onCleanup(() => Disposable.from(...disposables).dispose())
55-
})
56-
57-
watchEffect((onCleanup) => {
58-
if (!config.diagnostics.vulnerability)
59-
return
60-
61-
const provider = new VulnerabilityCodeActionProvider()
62-
const options = { providedCodeActionKinds: [CodeActionKind.QuickFix] }
63-
const disposables = extractorEntries.map(({ pattern }) =>
64-
languages.registerCodeActionsProvider({ pattern }, provider, options),
65-
)
66-
67-
onCleanup(() => Disposable.from(...disposables).dispose())
68-
})
69-
7043
useDiagnostics()
7144

45+
useCodeActions()
46+
7247
useCommands({
7348
[commands.openInBrowser]: openInBrowser,
7449
[commands.openFileInNpmx]: openFileInNpmx,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { extractorEntries } from '#extractors'
2+
import { config } from '#state'
3+
import { computed, watch } from 'reactive-vscode'
4+
import { CodeActionKind, Disposable, languages } from 'vscode'
5+
import { QuickFixProvider } from './quick-fix'
6+
7+
export function useCodeActions() {
8+
const hasQuickFix = computed(() => config.diagnostics.upgrade || config.diagnostics.vulnerability)
9+
10+
watch(hasQuickFix, (enabled, _, onCleanup) => {
11+
if (!enabled)
12+
return
13+
14+
const provider = new QuickFixProvider()
15+
const options = { providedCodeActionKinds: [CodeActionKind.QuickFix] }
16+
const disposables = extractorEntries.map(({ pattern }) =>
17+
languages.registerCodeActionsProvider({ pattern }, provider, options),
18+
)
19+
20+
onCleanup(() => Disposable.from(...disposables).dispose())
21+
}, { immediate: true })
22+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { CodeActionContext, CodeActionProvider, Diagnostic, Range, TextDocument } from 'vscode'
2+
import { CodeAction, CodeActionKind, WorkspaceEdit } from 'vscode'
3+
4+
interface QuickFixRule {
5+
pattern: RegExp
6+
title: (target: string) => string
7+
isPreferred?: boolean
8+
}
9+
10+
const quickFixRules: Record<string, QuickFixRule> = {
11+
upgrade: {
12+
pattern: /^New version available: (?<target>\S+)$/,
13+
title: (target) => `Update to ${target}`,
14+
},
15+
vulnerability: {
16+
pattern: / Upgrade to (?<target>\S+) to fix\.$/,
17+
title: (target) => `Update to ${target} to fix vulnerabilities`,
18+
isPreferred: true,
19+
},
20+
}
21+
22+
function getDiagnosticCodeValue(diagnostic: Diagnostic): string | undefined {
23+
if (typeof diagnostic.code === 'string')
24+
return diagnostic.code
25+
26+
if (typeof diagnostic.code === 'object' && typeof diagnostic.code.value === 'string')
27+
return diagnostic.code.value
28+
}
29+
30+
export class QuickFixProvider implements CodeActionProvider {
31+
provideCodeActions(document: TextDocument, _range: Range, context: CodeActionContext): CodeAction[] {
32+
return context.diagnostics.flatMap((diagnostic) => {
33+
const code = getDiagnosticCodeValue(diagnostic)
34+
if (!code)
35+
return []
36+
37+
const rule = quickFixRules[code]
38+
if (!rule)
39+
return []
40+
41+
const target = rule.pattern.exec(diagnostic.message)?.groups?.target
42+
if (!target)
43+
return []
44+
45+
const action = new CodeAction(rule.title(target), CodeActionKind.QuickFix)
46+
action.isPreferred = rule.isPreferred ?? false
47+
action.diagnostics = [diagnostic]
48+
action.edit = new WorkspaceEdit()
49+
action.edit.replace(document.uri, diagnostic.range, target)
50+
return [action]
51+
})
52+
}
53+
}

src/providers/code-actions/upgrade.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/providers/code-actions/vulnerability.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/providers/diagnostics/rules/upgrade.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { DependencyInfo } from '#types/extractor'
22
import type { ParsedVersion } from '#utils/version'
33
import type { DiagnosticRule, NodeDiagnosticInfo } from '..'
4-
import { UPGRADE_MESSAGE_PREFIX } from '#constants'
54
import { formatVersion, getPrereleaseId, isSupportedProtocol, lt, parseVersion } from '#utils/version'
65
import { DiagnosticSeverity } from 'vscode'
76

@@ -10,7 +9,8 @@ function createUpgradeDiagnostic(dep: DependencyInfo, parsed: ParsedVersion, upg
109
return {
1110
node: dep.versionNode,
1211
severity: DiagnosticSeverity.Hint,
13-
message: `${UPGRADE_MESSAGE_PREFIX}${target}`,
12+
message: `New version available: ${target}`,
13+
code: 'upgrade',
1414
}
1515
}
1616

0 commit comments

Comments
 (0)