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.
Files changed (77) hide show
  1. package/.vscodeignore +14 -0
  2. package/README.md +102 -0
  3. package/build.ts +14 -0
  4. package/docs/USAGE.md +213 -0
  5. package/examples/01-hover-help-text.ts +58 -0
  6. package/examples/02-codelens-annotations.ts +50 -0
  7. package/examples/03-code-actions.ts +70 -0
  8. package/examples/04-problems-panel.ts +67 -0
  9. package/examples/05-import-issues.ts +35 -0
  10. package/examples/06-all-severities.ts +60 -0
  11. package/examples/07-auto-fixable-vs-manual.ts +69 -0
  12. package/examples/08-disable-comments.ts +73 -0
  13. package/examples/09-clean-file.ts +65 -0
  14. package/examples/10-comprehensive-test.ts +153 -0
  15. package/examples/README.md +336 -0
  16. package/examples/advanced-config.ts +116 -0
  17. package/examples/basic-config.ts +38 -0
  18. package/examples/team-config.ts +179 -0
  19. package/examples/vscode-settings.json +88 -0
  20. package/package.json +231 -0
  21. package/src/code-actions.ts +310 -0
  22. package/src/codelens.ts +156 -0
  23. package/src/commands.ts +120 -0
  24. package/src/config.ts +128 -0
  25. package/src/diagnostics.ts +256 -0
  26. package/src/extension.ts +473 -0
  27. package/src/formatter.ts +75 -0
  28. package/src/hover.ts +108 -0
  29. package/src/index.ts +8 -0
  30. package/src/status-bar.ts +125 -0
  31. package/test/code-actions.test.ts +298 -0
  32. package/test/commands.test.ts +131 -0
  33. package/test/config.test.ts +223 -0
  34. package/test/diagnostics.test.ts +102 -0
  35. package/test/extension.test.ts +111 -0
  36. package/test/fixtures/edge-case-boundary.ts +101 -0
  37. package/test/fixtures/edge-case-comments.ts +50 -0
  38. package/test/fixtures/edge-case-multiline-constructs.ts +108 -0
  39. package/test/fixtures/edge-case-nested-structures.ts +78 -0
  40. package/test/fixtures/edge-case-real-world.ts +103 -0
  41. package/test/fixtures/edge-case-strings-templates.ts +56 -0
  42. package/test/fixtures/edge-case-unicode-special.ts +64 -0
  43. package/test/fixtures/fixable.ts +4 -0
  44. package/test/fixtures/format-indent.ts +22 -0
  45. package/test/fixtures/format-quotes.ts +12 -0
  46. package/test/fixtures/format-semi.ts +43 -0
  47. package/test/fixtures/format-whitespace.ts +19 -0
  48. package/test/fixtures/lint-cond-assign.ts +34 -0
  49. package/test/fixtures/lint-debugger-console.ts +34 -0
  50. package/test/fixtures/lint-errors.ts +10 -0
  51. package/test/fixtures/no-errors.ts +4 -0
  52. package/test/fixtures/pickier-import-dedupe.ts +14 -0
  53. package/test/fixtures/pickier-import-paths.ts +20 -0
  54. package/test/fixtures/pickier-no-unused-vars.ts +44 -0
  55. package/test/fixtures/pickier-prefer-const.ts +29 -0
  56. package/test/fixtures/pickier-sort-exports.ts +28 -0
  57. package/test/fixtures/pickier-sort-heritage-clauses.ts +40 -0
  58. package/test/fixtures/pickier-sort-imports.ts +22 -0
  59. package/test/fixtures/pickier-sort-objects.ts +37 -0
  60. package/test/fixtures/pickier-top-level-function.ts +37 -0
  61. package/test/fixtures/regexp-rules.ts +32 -0
  62. package/test/fixtures/style-consistent-chaining.ts +37 -0
  63. package/test/fixtures/style-consistent-list-newline.ts +49 -0
  64. package/test/fixtures/style-curly.ts +36 -0
  65. package/test/fixtures/style-if-newline.ts +34 -0
  66. package/test/fixtures/style-statements-per-line.ts +22 -0
  67. package/test/fixtures/ts-no-require.ts +21 -0
  68. package/test/fixtures/ts-no-top-level-await.ts +28 -0
  69. package/test/fixtures/ts-no-ts-export-equal.ts +19 -0
  70. package/test/fixtures/unordered-imports.ts +6 -0
  71. package/test/fixtures.test.ts +750 -0
  72. package/test/formatter.test.ts +79 -0
  73. package/test/index.test.ts +45 -0
  74. package/test/status-bar.test.ts +57 -0
  75. package/test/utils/pickier-mock.ts +14 -0
  76. package/test/utils/vscode-mock.ts +279 -0
  77. 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
+ }