Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 63 additions & 3 deletions javascript/packages/language-server/src/formatting_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Settings } from "./settings"
import { Config } from "@herb-tools/config"
import { version } from "../package.json"

const OPEN_CONFIG_ACTION = 'Open .herb.yml'

export class FormattingService {
private connection: Connection
private documents: TextDocuments<TextDocument>
Expand All @@ -16,6 +18,7 @@ export class FormattingService {
private config?: Config
private preRewriters: ASTRewriter[] = []
private postRewriters: StringRewriter[] = []
private failedRewriters: Map<string, string> = new Map()

constructor(connection: Connection, documents: TextDocuments<TextDocument>, project: Project, settings: Settings) {
this.connection = connection
Expand All @@ -27,6 +30,7 @@ export class FormattingService {
async initialize() {
try {
this.config = await Config.loadForEditor(this.project.projectPath, version)
this.connection.console.log(`[Formatting] Config loaded, formatter config: ${JSON.stringify(this.config?.formatter || 'none')}`)
await this.loadConfiguredRewriters()
this.connection.console.log("Herb formatter initialized successfully")
} catch (error) {
Expand All @@ -49,7 +53,12 @@ export class FormattingService {
}

private async loadConfiguredRewriters() {
this.connection.console.log(`[Rewriter] Loading rewriters from config: ${JSON.stringify(this.config?.formatter?.rewriter || 'none')}`)

this.failedRewriters.clear()

if (!this.config?.formatter?.rewriter) {
this.connection.console.log(`[Rewriter] No rewriter config found, clearing rewriters`)
this.preRewriters = []
this.postRewriters = []

Expand All @@ -58,6 +67,10 @@ export class FormattingService {

try {
const baseDir = this.config.projectPath || this.project.projectPath
this.connection.console.log(`[Rewriter] Using baseDir: ${baseDir}`)
this.connection.console.log(`[Rewriter] config.projectPath: ${this.config.projectPath}`)
this.connection.console.log(`[Rewriter] project.projectPath: ${this.project.projectPath}`)

const preNames = this.config.formatter.rewriter.pre || []
const postNames = this.config.formatter.rewriter.post || []
const warnings: string[] = []
Expand Down Expand Up @@ -105,7 +118,9 @@ export class FormattingService {

preRewriters.push(instance)
} catch (error) {
warnings.push(`Failed to initialize pre-format rewriter "${name}": ${error}`)
const errorMessage = error instanceof Error ? error.message : String(error)
warnings.push(`Failed to initialize pre-format rewriter "${name}": ${errorMessage}`)
this.failedRewriters.set(name, errorMessage)
}
}

Expand All @@ -130,15 +145,39 @@ export class FormattingService {

postRewriters.push(instance)
} catch (error) {
warnings.push(`Failed to initialize post-format rewriter "${name}": ${error}`)
const errorMessage = error instanceof Error ? error.message : String(error)
warnings.push(`Failed to initialize post-format rewriter "${name}": ${errorMessage}`)
this.failedRewriters.set(name, errorMessage)
}
}

this.preRewriters = preRewriters
this.postRewriters = postRewriters

this.connection.console.log(`[Rewriter] Loaded ${preRewriters.length} pre-rewriters: ${preRewriters.map(r => r.name).join(', ')}`)
this.connection.console.log(`[Rewriter] Loaded ${postRewriters.length} post-rewriters: ${postRewriters.map(r => r.name).join(', ')}`)

if (warnings.length > 0) {
warnings.forEach(warning => this.connection.console.warn(`Rewriter: ${warning}`))
// Log each warning individually
warnings.forEach(warning => {
this.connection.console.warn(`Rewriter: ${warning}`)
})

// Show a single combined warning message
const message = warnings.length === 1
? `Herb Rewriter: ${warnings[0]}`
: `Herb Rewriters (${warnings.length} failed):\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}`

if (this.settings.hasShowDocumentCapability) {
this.connection.window.showWarningMessage(message, { title: OPEN_CONFIG_ACTION }).then(action => {
if (action?.title === OPEN_CONFIG_ACTION) {
const configPath = `${this.project.projectPath}/.herb.yml`
this.connection.window.showDocument({ uri: `file://${configPath}`, takeFocus: true })
}
})
} else {
this.connection.window.showWarningMessage(message)
}
}
} catch (error) {
this.connection.console.error(`Failed to load rewriters: ${error}`)
Expand Down Expand Up @@ -203,6 +242,27 @@ export class FormattingService {
try {
const text = document.getText()
const config = await this.getConfigWithSettings(params.textDocument.uri)

this.connection.console.log(`[Formatting] Creating formatter with ${this.preRewriters.length} pre-rewriters, ${this.postRewriters.length} post-rewriters`)

if (this.failedRewriters.size > 0) {
const failedList = Array.from(this.failedRewriters.entries())
const message = failedList.length === 1
? `Herb Rewriter "${failedList[0][0]}" is not available: ${failedList[0][1]}`
: `Herb Rewriters (${failedList.length} not available):\n${failedList.map(([name, error], i) => `${i + 1}. ${name}: ${error}`).join('\n')}`

if (this.settings.hasShowDocumentCapability) {
this.connection.window.showWarningMessage(message, { title: OPEN_CONFIG_ACTION }).then(action => {
if (action?.title === OPEN_CONFIG_ACTION) {
const configPath = `${this.project.projectPath}/.herb.yml`
this.connection.window.showDocument({ uri: `file://${configPath}`, takeFocus: true })
}
})
} else {
this.connection.window.showWarningMessage(message)
}
}

const formatter = Formatter.from(this.project.herbBackend, config, {
preRewriters: this.preRewriters,
postRewriters: this.postRewriters
Expand Down
1 change: 1 addition & 0 deletions javascript/packages/language-server/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class Service {

async refresh() {
await this.project.refresh()
await this.formattingService.refreshConfig(this.config)
await this.diagnostics.refreshAllDocuments()
}

Expand Down
2 changes: 2 additions & 0 deletions javascript/packages/language-server/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class Settings {
hasConfigurationCapability = false
hasWorkspaceFolderCapability = false
hasDiagnosticRelatedInformationCapability = false
hasShowDocumentCapability = false

params: InitializeParams
capabilities: ClientCapabilities
Expand All @@ -50,6 +51,7 @@ export class Settings {
this.connection = connection

this.hasConfigurationCapability = !!(this.capabilities.workspace && !!this.capabilities.workspace.configuration)
this.hasShowDocumentCapability = !!(this.capabilities.window?.showDocument)

this.hasWorkspaceFolderCapability = !!(
this.capabilities.workspace && !!this.capabilities.workspace.workspaceFolders
Expand Down
1 change: 1 addition & 0 deletions javascript/packages/rewriter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"@herb-tools/core": "0.7.5",
"@herb-tools/tailwind-class-sorter": "0.7.5",
"glob": "^11.0.3"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,25 @@ export class TailwindClassSorterRewriter extends ASTRewriter {
}

async initialize(context: RewriteContext): Promise<void> {
this.sorter = await TailwindClassSorter.fromConfig({
baseDir: context.baseDir
})
try {
this.sorter = await TailwindClassSorter.fromConfig({
baseDir: context.baseDir
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)

if (errorMessage.includes('Cannot find module') || errorMessage.includes('ENOENT')) {
throw new Error(
`Tailwind CSS is not installed in this project. ` +
`To use the Tailwind class sorter, install Tailwind CSS itself using: npm install -D tailwindcss, ` +
`or remove the "tailwind-class-sorter" rewriter from your .herb.yml config file.\n` +
`If "tailwindcss" is already part of your package.json, make sure your NPM dependencies are installed.\n` +
`Original error: ${errorMessage}.`
)
}

throw error
}
}

rewrite<T extends Node>(node: T, _context: RewriteContext): T {
Expand Down
2 changes: 2 additions & 0 deletions javascript/packages/vscode/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class Client {
indentWidth: projectConfig.formatter?.indentWidth ?? 2,
maxLineLength: projectConfig.formatter?.maxLineLength ?? 80,
exclude: projectConfig.formatter?.exclude,
rewriter: projectConfig.formatter?.rewriter,
},
trace: {
server: vscodeConfig.get('trace.server', 'verbose'),
Expand Down Expand Up @@ -175,6 +176,7 @@ export class Client {
indentWidth: projectConfig.formatter?.indentWidth ?? 2,
maxLineLength: projectConfig.formatter?.maxLineLength ?? 80,
exclude: projectConfig.formatter?.exclude,
rewriter: projectConfig.formatter?.rewriter,
},
trace: {
server: vscodeConfig.get('trace.server', 'verbose'), // Trace is always from VS Code
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4276,7 +4276,7 @@ esbuild-plugin-copy@^2.1.1:
fs-extra "^10.0.1"
globby "^11.0.3"

esbuild@^0.25.0, esbuild@^0.25.11, esbuild@~0.25.0:
esbuild@^0.25.0, esbuild@~0.25.0:
version "0.25.11"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz"
integrity sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==
Expand Down
Loading