From 11bbb11fb70228d6b6dee5ba5d9fb2c85f22fa39 Mon Sep 17 00:00:00 2001 From: Daniel Brice Date: Wed, 27 Mar 2024 17:09:49 -0700 Subject: [PATCH] [DUX-2091] handle absolute paths in annotations --- src/annotations.ts | 142 +++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/src/annotations.ts b/src/annotations.ts index e9ebf76..69c05f9 100644 --- a/src/annotations.ts +++ b/src/annotations.ts @@ -47,7 +47,7 @@ export function makeAnnotations(output: vscode.OutputChannel, config: LanguageCo const quickFixes = vscode.languages.registerCodeActionsProvider( languageId, - { provideCodeActions: (document, range, context) => context.diagnostics.map(utils.asQuickFixes).flat() }, + { provideCodeActions: (document, range, context) => context.diagnostics.map(asQuickFixes).flat() }, { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] } ) @@ -61,16 +61,16 @@ export function makeAnnotations(output: vscode.OutputChannel, config: LanguageCo function watchAnnotationsFile(languageId: string, cfg: AnnotationsConfig): vscode.Disposable { const diagnostics = vscode.languages.createDiagnosticCollection(`${alloglot.collections.annotations}-${languageId}-${cfg.file}`) - const messagePath = utils.path(cfg.mapping.message) - const filePath = utils.path(cfg.mapping.file) - const startLinePath = utils.path(cfg.mapping.startLine) - const startColumnPath = utils.path(cfg.mapping.startColumn) - const endLinePath = utils.path(cfg.mapping.endLine) - const endColumnPath = utils.path(cfg.mapping.endColumn) - const sourcePath = utils.path(cfg.mapping.source) - const severityPath = utils.path(cfg.mapping.severity) - const replacementsPath = utils.path>(cfg.mapping.replacements) - const referenceCodePath = utils.path(cfg.mapping.referenceCode) + const messagePath = path(cfg.mapping.message) + const filePath = path(cfg.mapping.file) + const startLinePath = path(cfg.mapping.startLine) + const startColumnPath = path(cfg.mapping.startColumn) + const endLinePath = path(cfg.mapping.endLine) + const endColumnPath = path(cfg.mapping.endColumn) + const sourcePath = path(cfg.mapping.source) + const severityPath = path(cfg.mapping.severity) + const replacementsPath = path>(cfg.mapping.replacements) + const referenceCodePath = path(cfg.mapping.referenceCode) function marshalAnnotation(json: any): Annotation | undefined { const message = messagePath(json) @@ -90,7 +90,7 @@ function watchAnnotationsFile(languageId: string, cfg: AnnotationsConfig): vscod return { message, file, startLine, startColumn, endLine, endColumn, replacements, source: sourcePath(json) || `${cfg.file}`, - severity: utils.parseSeverity(severityPath(json)), + severity: parseSeverity(severityPath(json)), referenceCode: referenceCodePath(json)?.toString(), } } @@ -124,7 +124,13 @@ function watchAnnotationsFile(languageId: string, cfg: AnnotationsConfig): vscod const basedir = vscode.Uri.file(dirname(uri.fsPath)) vscode.workspace.fs.readFile(uri).then(bytes => { annotationsBySourceFile(readAnnotations(bytes)).forEach((anns, file) => { - diagnostics.set(vscode.Uri.joinPath(basedir, file), anns.map(ann => utils.annotationAsDiagnostic(basedir, ann))) + const path = file.startsWith('/') + ? vscode.Uri.file(file) + : file.startsWith('~') + ? vscode.Uri.file(file.replace('~', process.env.HOME || '')) + : vscode.Uri.joinPath(basedir, file) + + diagnostics.set(path, anns.map(ann => annotationAsDiagnostic(basedir, ann))) }) }) } @@ -143,67 +149,65 @@ function watchAnnotationsFile(languageId: string, cfg: AnnotationsConfig): vscod return vscode.Disposable.from(...watchers) } -namespace utils { - export function annotationAsDiagnostic(basedir: vscode.Uri, ann: Annotation): vscode.Diagnostic { - const range = new vscode.Range( - new vscode.Position(ann.startLine - 1, ann.startColumn - 1), - new vscode.Position(ann.endLine - 1, ann.endColumn - 1) - ) - - // we are abusing the relatedInformation field to store replacements - // we look them up later when we need to create quick fixes - const relatedInformation = ann.replacements.map(replacement => { - const uri = vscode.Uri.joinPath(basedir, ann.file) - const location = new vscode.Location(uri, range) - return new vscode.DiagnosticRelatedInformation(location, replacement) - }) +function annotationAsDiagnostic(basedir: vscode.Uri, ann: Annotation): vscode.Diagnostic { + const range = new vscode.Range( + new vscode.Position(ann.startLine - 1, ann.startColumn - 1), + new vscode.Position(ann.endLine - 1, ann.endColumn - 1) + ) - // i wish they gave an all-args constructor - const diagnostic = new vscode.Diagnostic(range, ann.message, asDiagnosticSeverity(ann.severity)) - diagnostic.source = ann.source - diagnostic.relatedInformation = relatedInformation - diagnostic.code = ann.referenceCode - return diagnostic - } + // we are abusing the relatedInformation field to store replacements + // we look them up later when we need to create quick fixes + const relatedInformation = ann.replacements.map(replacement => { + const uri = vscode.Uri.joinPath(basedir, ann.file) + const location = new vscode.Location(uri, range) + return new vscode.DiagnosticRelatedInformation(location, replacement) + }) + + // i wish they gave an all-args constructor + const diagnostic = new vscode.Diagnostic(range, ann.message, asDiagnosticSeverity(ann.severity)) + diagnostic.source = ann.source + diagnostic.relatedInformation = relatedInformation + diagnostic.code = ann.referenceCode + return diagnostic +} - function asDiagnosticSeverity(sev: Annotation['severity']): vscode.DiagnosticSeverity { - switch (sev) { - case 'error': return vscode.DiagnosticSeverity.Error - case 'warning': return vscode.DiagnosticSeverity.Warning - case 'info': return vscode.DiagnosticSeverity.Information - case 'hint': return vscode.DiagnosticSeverity.Hint - } +function asDiagnosticSeverity(sev: Annotation['severity']): vscode.DiagnosticSeverity { + switch (sev) { + case 'error': return vscode.DiagnosticSeverity.Error + case 'warning': return vscode.DiagnosticSeverity.Warning + case 'info': return vscode.DiagnosticSeverity.Information + case 'hint': return vscode.DiagnosticSeverity.Hint } +} - // this depends on the fact that we're abusing the `relatedInformation` field - // see `annotationAsDiagnostic` above - export function asQuickFixes(diag: vscode.Diagnostic): Array { - const actions = diag.relatedInformation?.map(info => { - const action = new vscode.CodeAction(diag.message, vscode.CodeActionKind.QuickFix) - action.diagnostics = [diag] - action.edit = new vscode.WorkspaceEdit - action.edit.replace(info.location.uri, info.location.range, info.message) - return action - }) - return actions || [] - } +// this depends on the fact that we're abusing the `relatedInformation` field +// see `annotationAsDiagnostic` above +function asQuickFixes(diag: vscode.Diagnostic): Array { + const actions = diag.relatedInformation?.map(info => { + const action = new vscode.CodeAction(diag.message, vscode.CodeActionKind.QuickFix) + action.diagnostics = [diag] + action.edit = new vscode.WorkspaceEdit + action.edit.replace(info.location.uri, info.location.range, info.message) + return action + }) + return actions || [] +} - export function path(keys: Array | undefined): (json: any) => T | undefined { - if (!keys) return () => undefined - else return json => { - const result = keys.reduce((acc, key) => acc?.[key], json) - if (result) return result as T - else return - } +function path(keys: Array | undefined): (json: any) => T | undefined { + if (!keys) return () => undefined + else return json => { + const result = keys.reduce((acc, key) => acc?.[key], json) + if (result) return result as T + else return undefined } +} - export function parseSeverity(raw: string | undefined): Annotation['severity'] { - if (!raw) return 'error' - const lower = raw.toLowerCase() - if (lower.includes('error')) return 'error' - if (lower.includes('warning')) return 'warning' - if (lower.includes('info')) return 'info' - if (lower.includes('hint')) return 'hint' - return 'error' - } +function parseSeverity(raw: string | undefined): Annotation['severity'] { + if (!raw) return 'error' + const lower = raw.toLowerCase() + if (lower.includes('error')) return 'error' + if (lower.includes('warning')) return 'warning' + if (lower.includes('info')) return 'info' + if (lower.includes('hint')) return 'hint' + return 'error' }