|
| 1 | +import type { DependencyInfo, Extractor, ValidNode } from '#types/extractor' |
| 2 | +import type { ResolvedPackument } from '#utils/npm' |
| 3 | +import type { Diagnostic, TextDocument } from 'vscode' |
| 4 | +import { basename } from 'node:path' |
| 5 | +import { logger } from '#state' |
| 6 | +import { getPackageInfo } from '#utils/npm' |
| 7 | +import { useActiveTextEditor, useDocumentText, watch } from 'reactive-vscode' |
| 8 | +import { languages } from 'vscode' |
| 9 | +import { displayName } from '../../generated-meta' |
| 10 | +import { checkDeprecations } from './rules/deprecation' |
| 11 | + |
| 12 | +export interface NodeDiagnosticInfo extends Pick<Diagnostic, 'message' | 'severity'> { |
| 13 | + node: ValidNode |
| 14 | +} |
| 15 | +export type DiagnosticRule = (dep: DependencyInfo, pkg: ResolvedPackument) => NodeDiagnosticInfo | undefined |
| 16 | + |
| 17 | +const rules: DiagnosticRule[] = [ |
| 18 | + checkDeprecations, |
| 19 | +] |
| 20 | + |
| 21 | +export function registerDiagnosticCollection(mapping: Record<string, Extractor | undefined>) { |
| 22 | + const diagnosticCollection = languages.createDiagnosticCollection(displayName) |
| 23 | + |
| 24 | + const activeEditor = useActiveTextEditor() |
| 25 | + const activeDocumentText = useDocumentText(() => activeEditor.value?.document) |
| 26 | + |
| 27 | + async function collectDiagnostics(document: TextDocument, extractor: Extractor) { |
| 28 | + const root = extractor.parse(document) |
| 29 | + if (!root) |
| 30 | + return |
| 31 | + |
| 32 | + const dependencies = extractor.getDependenciesInfo(root) |
| 33 | + const diagnostics: Diagnostic[] = [] |
| 34 | + |
| 35 | + await Promise.all( |
| 36 | + dependencies.map(async (dep) => { |
| 37 | + try { |
| 38 | + const pkg = await getPackageInfo(dep.name) |
| 39 | + |
| 40 | + for (const rule of rules) { |
| 41 | + const diagnostic = rule(dep, pkg) |
| 42 | + |
| 43 | + if (diagnostic) { |
| 44 | + diagnostics.push({ |
| 45 | + source: displayName, |
| 46 | + message: diagnostic.message, |
| 47 | + severity: diagnostic.severity, |
| 48 | + range: extractor.getNodeRange(document, diagnostic.node), |
| 49 | + }) |
| 50 | + } |
| 51 | + } |
| 52 | + } catch (err) { |
| 53 | + logger.warn(`Failed to check ${dep.name}: ${err}`) |
| 54 | + } |
| 55 | + }), |
| 56 | + ) |
| 57 | + |
| 58 | + diagnosticCollection.set(document.uri, diagnostics) |
| 59 | + } |
| 60 | + |
| 61 | + watch(activeDocumentText, async () => { |
| 62 | + const editor = activeEditor.value |
| 63 | + if (!editor) |
| 64 | + return |
| 65 | + |
| 66 | + const document = editor.document |
| 67 | + const filename = basename(document.fileName) |
| 68 | + const extractor = mapping[filename] |
| 69 | + |
| 70 | + if (extractor) |
| 71 | + await collectDiagnostics(document, extractor) |
| 72 | + }, { immediate: true }) |
| 73 | +} |
0 commit comments