diff --git a/javascript/packages/highlighter/src/diagnostic-renderer.ts b/javascript/packages/highlighter/src/diagnostic-renderer.ts index 9ec29788a..a5492fb92 100644 --- a/javascript/packages/highlighter/src/diagnostic-renderer.ts +++ b/javascript/packages/highlighter/src/diagnostic-renderer.ts @@ -14,6 +14,7 @@ export interface DiagnosticRenderOptions { maxWidth?: number truncateLines?: boolean codeUrl?: string + fileUrl?: string suffix?: string } @@ -142,9 +143,9 @@ export class DiagnosticRenderer { const shouldWrap = wrapLines && !truncateLines const shouldTruncate = truncateLines - const fileHeader = `${colorize(path, "cyan")}:${colorize(`${diagnostic.location.start.line}:${diagnostic.location.start.column}`, "cyan")}` - - const { codeUrl } = options + const fileHeaderText = `${colorize(path, "cyan")}:${colorize(`${diagnostic.location.start.line}:${diagnostic.location.start.column}`, "cyan")}` + const { codeUrl, fileUrl: fileUrlOption } = options + const fileHeader = fileUrlOption ? hyperlink(fileHeaderText, fileUrlOption) : fileHeaderText const color = severityColor(diagnostic.severity) const text = colorize(colorize(diagnostic.severity, color), "bold") diff --git a/javascript/packages/highlighter/src/highlighter.ts b/javascript/packages/highlighter/src/highlighter.ts index 26bf9ec7b..31b2b1167 100644 --- a/javascript/packages/highlighter/src/highlighter.ts +++ b/javascript/packages/highlighter/src/highlighter.ts @@ -21,6 +21,7 @@ export interface HighlightOptions { maxWidth?: number truncateLines?: boolean codeUrlBuilder?: (code: string) => string + fileUrlBuilder?: (path: string, diagnostic: Diagnostic) => string suffixBuilder?: (diagnostic: Diagnostic) => string | undefined } @@ -32,6 +33,7 @@ export interface HighlightDiagnosticOptions { maxWidth?: number truncateLines?: boolean codeUrl?: string + fileUrl?: string suffix?: string } @@ -102,6 +104,7 @@ export class Highlighter { maxWidth = LineWrapper.getTerminalWidth(), truncateLines = false, codeUrlBuilder, + fileUrlBuilder, suffixBuilder, } = options @@ -111,6 +114,7 @@ export class Highlighter { for (let i = 0; i < diagnostics.length; i++) { const diagnostic = diagnostics[i] const codeUrl = codeUrlBuilder && diagnostic.code ? codeUrlBuilder(diagnostic.code) : undefined + const fileUrl = fileUrlBuilder ? fileUrlBuilder(path, diagnostic) : undefined const suffix = suffixBuilder ? suffixBuilder(diagnostic) : undefined const result = this.highlightDiagnostic(path, diagnostic, content, { contextLines, @@ -119,6 +123,7 @@ export class Highlighter { maxWidth, truncateLines, codeUrl, + fileUrl, suffix, }) diff --git a/javascript/packages/linter/src/cli/formatters/detailed-formatter.ts b/javascript/packages/linter/src/cli/formatters/detailed-formatter.ts index 48d95efae..99d9b32e7 100644 --- a/javascript/packages/linter/src/cli/formatters/detailed-formatter.ts +++ b/javascript/packages/linter/src/cli/formatters/detailed-formatter.ts @@ -2,7 +2,7 @@ import { colorize, Highlighter, type ThemeInput, DEFAULT_THEME } from "@herb-too import { BaseFormatter } from "./base-formatter.js" import { LineWrapper } from "@herb-tools/highlighter" -import { ruleDocumentationUrl } from "../../urls.js" +import { ruleDocumentationUrl, fileUrl } from "../../urls.js" import type { Diagnostic } from "@herb-tools/core" import type { ProcessedFile } from "../file-processor.js" @@ -44,6 +44,7 @@ export class DetailedFormatter extends BaseFormatter { wrapLines: this.wrapLines, truncateLines: this.truncateLines, codeUrlBuilder: ruleDocumentationUrl, + fileUrlBuilder: (path) => fileUrl(path), suffixBuilder: (diagnostic) => autocorrectableSet.has(diagnostic) ? correctableTag : undefined, }) @@ -61,6 +62,7 @@ export class DetailedFormatter extends BaseFormatter { wrapLines: this.wrapLines, truncateLines: this.truncateLines, codeUrl, + fileUrl: fileUrl(filename), suffix, }) console.log(`\n${formatted}`) diff --git a/javascript/packages/linter/src/cli/formatters/simple-formatter.ts b/javascript/packages/linter/src/cli/formatters/simple-formatter.ts index 644dfa375..29060d3c9 100644 --- a/javascript/packages/linter/src/cli/formatters/simple-formatter.ts +++ b/javascript/packages/linter/src/cli/formatters/simple-formatter.ts @@ -1,7 +1,7 @@ import { colorize, hyperlink, TextFormatter } from "@herb-tools/highlighter" import { BaseFormatter } from "./base-formatter.js" -import { ruleDocumentationUrl } from "../../urls.js" +import { ruleDocumentationUrl, fileUrl } from "../../urls.js" import type { Diagnostic } from "@herb-tools/core" import type { ProcessedFile } from "../file-processor.js" @@ -25,35 +25,41 @@ export class SimpleFormatter extends BaseFormatter { } formatFile(filename: string, offenses: Diagnostic[]): void { - console.log(`${colorize(filename, "cyan")}:`) + const filenameText = colorize(filename, "cyan") + const filenameLink = hyperlink(filenameText, fileUrl(filename)) + console.log(`${filenameLink}:`) for (const offense of offenses) { const isError = offense.severity === "error" const severity = isError ? colorize("✗", "brightRed") : colorize("⚠", "brightYellow") const ruleText = `(${offense.code})` const rule = offense.code ? hyperlink(ruleText, ruleDocumentationUrl(offense.code)) : ruleText - const locationString = `${offense.location.start.line}:${offense.location.start.column}` - const paddedLocation = locationString.padEnd(4) - + const { line, column } = offense.location.start + const locationString = `${line}:${column}` + const paddedLocation = colorize(locationString.padEnd(4), "gray") const message = TextFormatter.highlightBackticks(offense.message) - console.log(` ${colorize(paddedLocation, "gray")} ${severity} ${message} ${rule}`) + + console.log(` ${paddedLocation} ${severity} ${message} ${rule}`) } } formatFileProcessed(filename: string, processedFiles: ProcessedFile[]): void { - console.log(`${colorize(filename, "cyan")}:`) + const filenameText = colorize(filename, "cyan") + const filenameLink = hyperlink(filenameText, fileUrl(filename)) + console.log(`${filenameLink}:`) for (const { offense, autocorrectable } of processedFiles) { const isError = offense.severity === "error" const severity = isError ? colorize("✗", "brightRed") : colorize("⚠", "brightYellow") const ruleText = `(${offense.code})` const rule = offense.code ? hyperlink(ruleText, ruleDocumentationUrl(offense.code)) : ruleText - const locationString = `${offense.location.start.line}:${offense.location.start.column}` - const paddedLocation = locationString.padEnd(4) + const { line, column } = offense.location.start + const locationString = `${line}:${column}` + const paddedLocation = colorize(locationString.padEnd(4), "gray") const correctable = autocorrectable ? colorize(colorize(" [Correctable]", "green"), "bold") : "" - const message = TextFormatter.highlightBackticks(offense.message) - console.log(` ${colorize(paddedLocation, "gray")} ${severity} ${message} ${rule}${correctable}`) + + console.log(` ${paddedLocation} ${severity} ${message} ${rule}${correctable}`) } } } diff --git a/javascript/packages/linter/src/index.ts b/javascript/packages/linter/src/index.ts index 96dab9eb3..cbab52b1f 100644 --- a/javascript/packages/linter/src/index.ts +++ b/javascript/packages/linter/src/index.ts @@ -1,6 +1,6 @@ export * from "./linter.js" export * from "./rules/index.js" export * from "./types.js" -export * from "./urls.js" +export { ruleDocumentationUrl } from "./urls.js" export { rules } from "./rules.js" diff --git a/javascript/packages/linter/src/urls.ts b/javascript/packages/linter/src/urls.ts index d5009c0d4..d1541314f 100644 --- a/javascript/packages/linter/src/urls.ts +++ b/javascript/packages/linter/src/urls.ts @@ -1,5 +1,12 @@ +import { resolve } from "node:path" + const DOCS_BASE_URL = "https://herb-tools.dev/linter/rules" export function ruleDocumentationUrl(ruleId: string): string { return `${DOCS_BASE_URL}/${ruleId}` } + +export function fileUrl(filePath: string): string { + const absolutePath = resolve(filePath) + return `file://${absolutePath}` +}