pickier-vscode 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscodeignore +14 -0
- package/README.md +102 -0
- package/build.ts +14 -0
- package/docs/USAGE.md +213 -0
- package/examples/01-hover-help-text.ts +58 -0
- package/examples/02-codelens-annotations.ts +50 -0
- package/examples/03-code-actions.ts +70 -0
- package/examples/04-problems-panel.ts +67 -0
- package/examples/05-import-issues.ts +35 -0
- package/examples/06-all-severities.ts +60 -0
- package/examples/07-auto-fixable-vs-manual.ts +69 -0
- package/examples/08-disable-comments.ts +73 -0
- package/examples/09-clean-file.ts +65 -0
- package/examples/10-comprehensive-test.ts +153 -0
- package/examples/README.md +336 -0
- package/examples/advanced-config.ts +116 -0
- package/examples/basic-config.ts +38 -0
- package/examples/team-config.ts +179 -0
- package/examples/vscode-settings.json +88 -0
- package/package.json +231 -0
- package/src/code-actions.ts +310 -0
- package/src/codelens.ts +156 -0
- package/src/commands.ts +120 -0
- package/src/config.ts +128 -0
- package/src/diagnostics.ts +256 -0
- package/src/extension.ts +473 -0
- package/src/formatter.ts +75 -0
- package/src/hover.ts +108 -0
- package/src/index.ts +8 -0
- package/src/status-bar.ts +125 -0
- package/test/code-actions.test.ts +298 -0
- package/test/commands.test.ts +131 -0
- package/test/config.test.ts +223 -0
- package/test/diagnostics.test.ts +102 -0
- package/test/extension.test.ts +111 -0
- package/test/fixtures/edge-case-boundary.ts +101 -0
- package/test/fixtures/edge-case-comments.ts +50 -0
- package/test/fixtures/edge-case-multiline-constructs.ts +108 -0
- package/test/fixtures/edge-case-nested-structures.ts +78 -0
- package/test/fixtures/edge-case-real-world.ts +103 -0
- package/test/fixtures/edge-case-strings-templates.ts +56 -0
- package/test/fixtures/edge-case-unicode-special.ts +64 -0
- package/test/fixtures/fixable.ts +4 -0
- package/test/fixtures/format-indent.ts +22 -0
- package/test/fixtures/format-quotes.ts +12 -0
- package/test/fixtures/format-semi.ts +43 -0
- package/test/fixtures/format-whitespace.ts +19 -0
- package/test/fixtures/lint-cond-assign.ts +34 -0
- package/test/fixtures/lint-debugger-console.ts +34 -0
- package/test/fixtures/lint-errors.ts +10 -0
- package/test/fixtures/no-errors.ts +4 -0
- package/test/fixtures/pickier-import-dedupe.ts +14 -0
- package/test/fixtures/pickier-import-paths.ts +20 -0
- package/test/fixtures/pickier-no-unused-vars.ts +44 -0
- package/test/fixtures/pickier-prefer-const.ts +29 -0
- package/test/fixtures/pickier-sort-exports.ts +28 -0
- package/test/fixtures/pickier-sort-heritage-clauses.ts +40 -0
- package/test/fixtures/pickier-sort-imports.ts +22 -0
- package/test/fixtures/pickier-sort-objects.ts +37 -0
- package/test/fixtures/pickier-top-level-function.ts +37 -0
- package/test/fixtures/regexp-rules.ts +32 -0
- package/test/fixtures/style-consistent-chaining.ts +37 -0
- package/test/fixtures/style-consistent-list-newline.ts +49 -0
- package/test/fixtures/style-curly.ts +36 -0
- package/test/fixtures/style-if-newline.ts +34 -0
- package/test/fixtures/style-statements-per-line.ts +22 -0
- package/test/fixtures/ts-no-require.ts +21 -0
- package/test/fixtures/ts-no-top-level-await.ts +28 -0
- package/test/fixtures/ts-no-ts-export-equal.ts +19 -0
- package/test/fixtures/unordered-imports.ts +6 -0
- package/test/fixtures.test.ts +750 -0
- package/test/formatter.test.ts +79 -0
- package/test/index.test.ts +45 -0
- package/test/status-bar.test.ts +57 -0
- package/test/utils/pickier-mock.ts +14 -0
- package/test/utils/vscode-mock.ts +279 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Dynamic imports will be used to avoid bundling issues
|
|
2
|
+
import * as vscode from 'vscode'
|
|
3
|
+
|
|
4
|
+
interface LintResult {
|
|
5
|
+
errors: number
|
|
6
|
+
warnings: number
|
|
7
|
+
issues: Array<{
|
|
8
|
+
filePath: string
|
|
9
|
+
line: number
|
|
10
|
+
column: number
|
|
11
|
+
ruleId: string
|
|
12
|
+
message: string
|
|
13
|
+
severity: 'warning' | 'error'
|
|
14
|
+
help?: string
|
|
15
|
+
}>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Bridge: VS Code CancellationToken -> AbortSignal
|
|
19
|
+
function tokenToAbortSignal(token: vscode.CancellationToken): AbortSignal {
|
|
20
|
+
const controller = new AbortController()
|
|
21
|
+
token.onCancellationRequested(() => controller.abort())
|
|
22
|
+
return controller.signal
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class PickierDiagnosticProvider {
|
|
26
|
+
constructor(
|
|
27
|
+
private diagnosticCollection: vscode.DiagnosticCollection,
|
|
28
|
+
private outputChannel: vscode.OutputChannel,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async provideDiagnostics(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<void> {
|
|
32
|
+
const config = vscode.workspace.getConfiguration('pickier')
|
|
33
|
+
if (!config.get('enable', true)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Clear existing diagnostics for this document
|
|
38
|
+
this.diagnosticCollection.delete(document.uri)
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (token?.isCancellationRequested)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
const diagnostics = await this.lintDocument(document, token)
|
|
45
|
+
|
|
46
|
+
if (token?.isCancellationRequested)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
this.diagnosticCollection.set(document.uri, diagnostics)
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
53
|
+
this.outputChannel.appendLine(`Lint error for ${document.fileName}: ${errorMessage}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async lintDocument(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
|
|
58
|
+
const documentText = document.getText()
|
|
59
|
+
|
|
60
|
+
// Prefer programmatic API if available to avoid stdout capture and temp files
|
|
61
|
+
try {
|
|
62
|
+
const mod: any = await import('pickier')
|
|
63
|
+
const cfg = await (async () => mod.defaultConfig as any)()
|
|
64
|
+
if (typeof mod.lintText === 'function') {
|
|
65
|
+
const signal = token ? tokenToAbortSignal(token) : undefined
|
|
66
|
+
const issues = await mod.lintText(documentText, cfg, document.fileName, signal)
|
|
67
|
+
if (token?.isCancellationRequested)
|
|
68
|
+
return []
|
|
69
|
+
return issues.map(issue => convertIssueToDiagnostic(issue, documentText, document.uri))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
// If dynamic import or programmatic API fails, fall back to stdout approach
|
|
74
|
+
this.outputChannel.appendLine(`Programmatic lint unavailable, falling back. Reason: ${(e as any)?.message || e}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Fallback: temp file + stdout JSON capture
|
|
78
|
+
const options = { reporter: 'json' as const, maxWarnings: -1 }
|
|
79
|
+
const lintResult = await runPickierAndParseJson([document.fileName], options, this.outputChannel, token, documentText)
|
|
80
|
+
if (token?.isCancellationRequested)
|
|
81
|
+
return []
|
|
82
|
+
return lintResult.issues.map(issue => convertIssueToDiagnostic(issue, documentText, document.uri))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Helper: run pickier on paths and return parsed JSON
|
|
87
|
+
async function runPickierAndParseJson(
|
|
88
|
+
paths: string[],
|
|
89
|
+
options: { reporter: 'json', maxWarnings: number },
|
|
90
|
+
output: vscode.OutputChannel,
|
|
91
|
+
token?: vscode.CancellationToken,
|
|
92
|
+
docTextForActive?: string,
|
|
93
|
+
): Promise<LintResult> {
|
|
94
|
+
// Capture stdout to get JSON results
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
const originalLog = console.log
|
|
97
|
+
const originalError = console.error
|
|
98
|
+
let capturedOutput = ''
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.log = (message: string) => {
|
|
102
|
+
capturedOutput += `${message}\n`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.error = () => {} // Suppress error output
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const mod: any = await import('pickier')
|
|
109
|
+
if (token?.isCancellationRequested)
|
|
110
|
+
return { errors: 0, warnings: 0, issues: [] }
|
|
111
|
+
// If only a single path represents the active unsaved doc, prefer lintText when available
|
|
112
|
+
if (docTextForActive && typeof mod.lintText === 'function') {
|
|
113
|
+
const signal = token ? tokenToAbortSignal(token) : undefined
|
|
114
|
+
const issues = await mod.lintText(docTextForActive, mod.defaultConfig, paths[0], signal)
|
|
115
|
+
return { errors: issues.filter((i: any) => i.severity === 'error').length, warnings: issues.filter((i: any) => i.severity === 'warning').length, issues }
|
|
116
|
+
}
|
|
117
|
+
await mod.runLint(paths, options)
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.log = originalLog
|
|
122
|
+
console.error = originalError
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Parse the JSON output
|
|
126
|
+
try {
|
|
127
|
+
if (token?.isCancellationRequested)
|
|
128
|
+
return { errors: 0, warnings: 0, issues: [] }
|
|
129
|
+
const jsonMatch = capturedOutput.match(/\{[\s\S]*\}/)
|
|
130
|
+
if (jsonMatch) {
|
|
131
|
+
return JSON.parse(jsonMatch[0]) as LintResult
|
|
132
|
+
}
|
|
133
|
+
return { errors: 0, warnings: 0, issues: [] }
|
|
134
|
+
}
|
|
135
|
+
catch (parseError) {
|
|
136
|
+
output.appendLine(`Failed to parse lint results: ${parseError}`)
|
|
137
|
+
return { errors: 0, warnings: 0, issues: [] }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Helper: convert a single issue to a VS Code diagnostic
|
|
142
|
+
function convertIssueToDiagnostic(issue: LintResult['issues'][number], documentText?: string, documentUri?: vscode.Uri): vscode.Diagnostic {
|
|
143
|
+
const line = Math.max(0, issue.line - 1)
|
|
144
|
+
const column = Math.max(0, issue.column - 1)
|
|
145
|
+
|
|
146
|
+
// Try to create a better range that underlines the problematic token/word
|
|
147
|
+
let range: vscode.Range
|
|
148
|
+
if (documentText) {
|
|
149
|
+
const lines = documentText.split(/\r?\n/)
|
|
150
|
+
if (line < lines.length) {
|
|
151
|
+
const lineText = lines[line]
|
|
152
|
+
const startCol = column
|
|
153
|
+
|
|
154
|
+
// Find the end of the current word/token for better underlining
|
|
155
|
+
let endCol = startCol + 1
|
|
156
|
+
|
|
157
|
+
// Check if we're at a word character
|
|
158
|
+
if (startCol < lineText.length && /\w/.test(lineText[startCol])) {
|
|
159
|
+
// Extend to the end of the word
|
|
160
|
+
while (endCol < lineText.length && /\w/.test(lineText[endCol])) {
|
|
161
|
+
endCol++
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// For non-word characters, underline at least 3 characters or until end of line
|
|
166
|
+
endCol = Math.min(startCol + 3, lineText.length)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
range = new vscode.Range(
|
|
170
|
+
new vscode.Position(line, startCol),
|
|
171
|
+
new vscode.Position(line, endCol),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Fallback if line is out of bounds
|
|
176
|
+
range = new vscode.Range(
|
|
177
|
+
new vscode.Position(line, column),
|
|
178
|
+
new vscode.Position(line, column + 1),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Fallback when we don't have document text
|
|
184
|
+
range = new vscode.Range(
|
|
185
|
+
new vscode.Position(line, column),
|
|
186
|
+
new vscode.Position(line, column + 1),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const severity = issue.severity === 'error'
|
|
191
|
+
? vscode.DiagnosticSeverity.Error
|
|
192
|
+
: vscode.DiagnosticSeverity.Warning
|
|
193
|
+
const diagnostic = new vscode.Diagnostic(range, issue.message, severity)
|
|
194
|
+
diagnostic.source = 'pickier'
|
|
195
|
+
diagnostic.code = issue.ruleId
|
|
196
|
+
|
|
197
|
+
// Add help text as related information (for hover and problems panel)
|
|
198
|
+
if (issue.help && documentUri) {
|
|
199
|
+
diagnostic.relatedInformation = [
|
|
200
|
+
new vscode.DiagnosticRelatedInformation(
|
|
201
|
+
new vscode.Location(documentUri, range),
|
|
202
|
+
issue.help,
|
|
203
|
+
),
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Add tags for better visual feedback
|
|
208
|
+
const tags: vscode.DiagnosticTag[] = []
|
|
209
|
+
|
|
210
|
+
// Mark unused code with a fade-out effect
|
|
211
|
+
if (issue.ruleId.includes('no-unused') || issue.ruleId.includes('unused')) {
|
|
212
|
+
tags.push(vscode.DiagnosticTag.Unnecessary)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Mark deprecated code
|
|
216
|
+
if (issue.ruleId.includes('deprecated') || issue.message.toLowerCase().includes('deprecated')) {
|
|
217
|
+
tags.push(vscode.DiagnosticTag.Deprecated)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (tags.length > 0) {
|
|
221
|
+
diagnostic.tags = tags
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return diagnostic
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Exported helper: lint multiple file paths and map diagnostics by file
|
|
228
|
+
export async function lintPathsToDiagnostics(paths: string[], output: vscode.OutputChannel): Promise<Record<string, vscode.Diagnostic[]>> {
|
|
229
|
+
const options = { reporter: 'json' as const, maxWarnings: -1 }
|
|
230
|
+
// Try programmatic batch lint first
|
|
231
|
+
try {
|
|
232
|
+
const mod: any = await import('pickier')
|
|
233
|
+
if (typeof mod.runLintProgrammatic === 'function') {
|
|
234
|
+
const res = await mod.runLintProgrammatic(paths, options)
|
|
235
|
+
const map: Record<string, vscode.Diagnostic[]> = {}
|
|
236
|
+
for (const issue of res.issues) {
|
|
237
|
+
const list = map[issue.filePath] || (map[issue.filePath] = [])
|
|
238
|
+
const uri = vscode.Uri.file(issue.filePath)
|
|
239
|
+
list.push(convertIssueToDiagnostic(issue, undefined, uri))
|
|
240
|
+
}
|
|
241
|
+
return map
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
output.appendLine(`Programmatic workspace lint unavailable, falling back. Reason: ${(e as any)?.message || e}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = await runPickierAndParseJson(paths, options, output)
|
|
249
|
+
const map: Record<string, vscode.Diagnostic[]> = {}
|
|
250
|
+
for (const issue of result.issues) {
|
|
251
|
+
const list = map[issue.filePath] || (map[issue.filePath] = [])
|
|
252
|
+
const uri = vscode.Uri.file(issue.filePath)
|
|
253
|
+
list.push(convertIssueToDiagnostic(issue, undefined, uri))
|
|
254
|
+
}
|
|
255
|
+
return map
|
|
256
|
+
}
|