From 0a93c318b89b983aa15c8cf835d2540702249307 Mon Sep 17 00:00:00 2001 From: Ali Ariff Date: Sat, 31 Aug 2024 16:14:15 +0200 Subject: [PATCH] Add config to ignore formatting (#72) * feat: add config to ignore formatting * docs: add config to ignore formatting * docs: improve & refactor * docs: align table * docs: update formatting * test: add test for ignore formatting * refactor: Refactor ERB Formatter/Beautify tests * refactor: Update HtmlBeautifierProvider to format the entire document * test: refactor * refactor: Add return types to configuration change functions * refactor: Update HtmlBeautifier class to use public logChannel * refactor: Improve error handling in HtmlBeautifier class * refactor: Improve error handling in HtmlBeautifier class * refactor: Simplify error handling in HtmlBeautifier class * refactor: Simplify HtmlBeautifier command execution --- README.md | 109 +++++++++++++------ package-lock.json | 27 +++-- package.json | 12 ++- src/formatter/htmlbeautifier.ts | 102 ++++-------------- src/formatter/htmlbeautifierProvider.ts | 83 +++++++++------ src/test/extension.test.ts | 134 ++++++++++++++---------- src/test/fixtures/ignored_file.text.erb | 3 + 7 files changed, 261 insertions(+), 209 deletions(-) create mode 100644 src/test/fixtures/ignored_file.text.erb diff --git a/README.md b/README.md index 453fdea..7da53ce 100644 --- a/README.md +++ b/README.md @@ -4,73 +4,114 @@   [![Release](https://github.com/aliariff/vscode-erb-beautify/actions/workflows/release.yaml/badge.svg)](https://github.com/aliariff/vscode-erb-beautify/actions/workflows/release.yaml) -Most of solution that exist in the internet is tell you to create a task and call it using ctrl-shift-p menu. +## Overview -This extension basically using [htmlbeautifier](https://github.com/threedaymonk/htmlbeautifier) to format your file using the Formatter API from the vscode, so no need to create a hack using Task, etc. +The **ERB Formatter/Beautify** extension for Visual Studio Code uses the [htmlbeautifier](https://github.com/threedaymonk/htmlbeautifier) gem to format ERB files, providing a seamless experience with the VS Code Formatter API. Unlike other solutions that require setting up tasks or manual formatting, this extension integrates directly with VS Code to format your ERB files automatically. ## Features +- Formats ERB files using `htmlbeautifier`. +- Works with the VS Code Formatter API for a native experience. +- Supports custom configuration options for indentation, blank lines, and error handling. +- Ability to ignore specific files or patterns. + ![Demo GIF](./images/demo.gif) -## Requirements +## Installation -``` +### Requirements + +To use this extension, you must have `htmlbeautifier` installed on your system. You can install it globally or add it to your project's Gemfile. + +#### Install Globally + +```bash gem install htmlbeautifier ``` -or +#### Install with Bundler -To use the gem with Bundler, add to your `Gemfile`: +Add the following to your `Gemfile`: ```ruby gem 'htmlbeautifier' ``` -NOTE: For you that have a filename with extension `.html.erb`, your file might be recognized as `html` file, not as `erb` file. In that case, add a setting in your `settings.json` like below: +Then run: + +```bash +bundle install +``` + +## Configuration + +### Resolving File Recognition Issues + +If `.html.erb` files are recognized as HTML instead of ERB, add the following to your `settings.json` to associate `.html.erb` files with the ERB language: + +```json +"files.associations": { + "*.html.erb": "erb" +} +``` + +### Setting Default Formatter and Enabling Format-on-Save + +To set the default formatter for ERB files and enable format-on-save, add the following to your `settings.json`: ```json "[erb]": { "editor.defaultFormatter": "aliariff.vscode-erb-beautify", "editor.formatOnSave": true -}, -"files.associations": { - "*.html.erb": "erb" } ``` -## Known Issues +This ensures that the extension formats ERB files automatically whenever they are saved. -- `invalid byte sequence in US-ASCII` +### Disabling Formatting for Specific Files - Add below setting. [Reference](https://github.com/aliariff/vscode-erb-beautify/issues/47) +To disable formatting for specific ERB files, such as email templates, use the `ignoreFormatFilePatterns` setting. Add the following to your `settings.json`: - ```json - "vscode-erb-beautify.customEnvVar": { - "LC_ALL": "en_US.UTF-8" - } - ``` +```json +"vscode-erb-beautify.ignoreFormatFilePatterns": ["**/email_templates/**/*.erb"] +``` -## Settings +This configuration ignores all `.erb` files inside the `email_templates` directory. -| Setting | Description | Default | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `vscode-erb-beautify.executePath` | Path to the htmlbeautifier executable, set this to absolute path when you have different htmlbeautifier location | htmlbeautifier | -| `vscode-erb-beautify.useBundler` | Execute htmlbeautifier using bundler (ie 'bundle exec htmlbeautifier'). If this true, vscode-erb-beautify.executePath is ignored | false | -| `vscode-erb-beautify.bundlerPath` | Path to the bundler executable, set this to absolute path when you have different bundler location | bundle | -| `vscode-erb-beautify.tabStops` | Set number of spaces per indent | 2 | -| `vscode-erb-beautify.tab` | Indent using tabs | false | -| `vscode-erb-beautify.indentBy` | Indent the output by NUMBER steps | 0 | -| `vscode-erb-beautify.stopOnErrors` | Stop when invalid nesting is encountered in the input | false | -| `vscode-erb-beautify.keepBlankLines` | Set number of consecutive blank lines | 0 | -| `vscode-erb-beautify.customEnvVar` | Custom environment variables to pass to the htmlbeautifier | {} | +### Fixing Encoding Issues -## References +If you encounter the `Invalid byte sequence in US-ASCII` error, add the following setting to your `settings.json` to set the correct locale: -[Issue](https://github.com/threedaymonk/htmlbeautifier/issues/49) +```json +"vscode-erb-beautify.customEnvVar": { + "LC_ALL": "en_US.UTF-8" +} +``` + +For more details, see the [related issue](https://github.com/aliariff/vscode-erb-beautify/issues/47). -[Issue](https://github.com/rubyide/vscode-ruby/issues/56) +## Settings + +Below is a list of settings you can configure in your `settings.json` file: + +| Setting | Description | Default | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `vscode-erb-beautify.executePath` | Path to the `htmlbeautifier` executable. Set this to an absolute path if `htmlbeautifier` is installed in a non-standard location. | `htmlbeautifier` | +| `vscode-erb-beautify.useBundler` | Execute `htmlbeautifier` using Bundler (e.g., `bundle exec htmlbeautifier`). If true, `vscode-erb-beautify.executePath` is ignored. | `false` | +| `vscode-erb-beautify.bundlerPath` | Path to the Bundler executable. Set this to an absolute path if Bundler is installed in a non-standard location. | `bundle` | +| `vscode-erb-beautify.tabStops` | Number of spaces per indent. | `2` | +| `vscode-erb-beautify.tab` | Indent using tabs instead of spaces. | `false` | +| `vscode-erb-beautify.indentBy` | Indent the output by a specified number of steps. | `0` | +| `vscode-erb-beautify.stopOnErrors` | Stop formatting when invalid nesting is encountered in the input. | `false` | +| `vscode-erb-beautify.keepBlankLines` | Number of consecutive blank lines to keep in the formatted output. | `0` | +| `vscode-erb-beautify.customEnvVar` | Custom environment variables to pass to `htmlbeautifier`. | `{}` | +| `vscode-erb-beautify.ignoreFormatFilePatterns` | Glob patterns for files to ignore during formatting. | `[]` | + +## References -[Ref](https://medium.com/@costa.alexoglou/enable-formatting-with-erb-files-in-vscode-d4b4ff537017) +- [Issue on `htmlbeautifier`](https://github.com/threedaymonk/htmlbeautifier/issues/49) +- [VS Code Ruby Extension Issue](https://github.com/rubyide/vscode-ruby/issues/56) +- [Enable Formatting with ERB Files in VS Code](https://medium.com/@costa.alexoglou/enable-formatting-with-erb-files-in-vscode-d4b4ff537017) ## Credits diff --git a/package-lock.json b/package-lock.json index 0159cac..2a62fc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "0.4.4", "license": "MIT", "dependencies": { - "is-wsl": "^2.2.0" + "is-wsl": "^2.2.0", + "micromatch": "^4.0.8" }, "devDependencies": { "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", + "@types/micromatch": "^4.0.9", "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/vscode": "^1.80.0", @@ -1031,6 +1033,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/braces": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.4.tgz", + "integrity": "sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1038,6 +1047,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/micromatch": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", + "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/braces": "*" + } + }, "node_modules/@types/mocha": { "version": "10.0.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", @@ -1807,7 +1826,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -3197,7 +3215,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3983,7 +4000,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -4562,7 +4578,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -7971,7 +7986,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -9455,7 +9469,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, diff --git a/package.json b/package.json index b20a28e..8e3092c 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,14 @@ "type": "object", "default": {}, "description": "Custom environment variables to pass to the htmlbeautifier" + }, + "vscode-erb-beautify.ignoreFormatFilePatterns": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "Ignore formatting for files matching these patterns" } } } @@ -97,6 +105,7 @@ "devDependencies": { "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", + "@types/micromatch": "^4.0.9", "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/vscode": "^1.80.0", @@ -110,6 +119,7 @@ "typescript": "^5.4.5" }, "dependencies": { - "is-wsl": "^2.2.0" + "is-wsl": "^2.2.0", + "micromatch": "^4.0.8" } } diff --git a/src/formatter/htmlbeautifier.ts b/src/formatter/htmlbeautifier.ts index 33acd52..21ae10c 100644 --- a/src/formatter/htmlbeautifier.ts +++ b/src/formatter/htmlbeautifier.ts @@ -3,7 +3,7 @@ import * as cp from "child_process"; const isWsl = require("is-wsl"); export default class HtmlBeautifier { - private logChannel: vscode.LogOutputChannel; + public logChannel: vscode.LogOutputChannel; constructor() { this.logChannel = vscode.window.createOutputChannel("ERB Beautifier", { @@ -17,18 +17,11 @@ export default class HtmlBeautifier { * @returns A promise that resolves to the formatted string. */ public async format(input: string): Promise { - try { - const startTime = Date.now(); - const result = await this.executeCommand(input); - const duration = Date.now() - startTime; - this.logChannel.info( - `Formatting completed successfully in ${duration}ms.` - ); - return result; - } catch (error) { - this.handleError(error, "Error occurred while formatting"); - throw error; - } + const startTime = Date.now(); + const result = await this.executeCommand(input); + const duration = Date.now() - startTime; + this.logChannel.info(`Formatting completed successfully in ${duration}ms.`); + return result; } /** @@ -49,16 +42,13 @@ export default class HtmlBeautifier { ...shellOptions, }); - const fullCommand = `${this.exe} ${this.cliOptions.join(" ")} (cwd: ${ - vscode.workspace.rootPath || __dirname - }) with custom env: ${JSON.stringify(this.customEnvVars)}`; + const fullCommand = `${this.exe} ${this.cliOptions.join( + " " + )} with custom env: ${JSON.stringify(this.customEnvVars)}`; this.logChannel.info(`Formatting ERB with command: ${fullCommand}`); if (!htmlbeautifier.stdin || !htmlbeautifier.stdout) { - return this.handleSpawnError( - reject, - "Couldn't initialize STDIN or STDOUT" - ); + reject("Failed to spawn process, missing stdin/stdout"); } const stdoutChunks: Buffer[] = []; @@ -67,19 +57,19 @@ export default class HtmlBeautifier { htmlbeautifier.stdout.on("data", (chunk) => stdoutChunks.push(chunk)); htmlbeautifier.stderr.on("data", (chunk) => stderrChunks.push(chunk)); - htmlbeautifier.on("error", (err) => - this.handleSpawnError( - reject, - `Couldn't run ${this.exe}: ${err.message}`, - err - ) - ); + htmlbeautifier.on("error", (error) => { + reject(error); + }); htmlbeautifier.on("exit", (code) => { const formattedResult = Buffer.concat(stdoutChunks).toString(); const finalResult = this.handleFinalNewline(input, formattedResult); const errorMessage = Buffer.concat(stderrChunks).toString(); - this.handleExit(code, finalResult, errorMessage, resolve, reject); + if (code && code !== 0) { + reject(`Failed with exit code ${code}. ${errorMessage}`); + } else { + resolve(finalResult); + } }); htmlbeautifier.stdin.write(input); @@ -87,62 +77,6 @@ export default class HtmlBeautifier { }); } - /** - * Handles errors during process spawning. - * @param reject The promise reject function. - * @param message The error message to log and show to the user. - * @param err Optional error object. - */ - private handleSpawnError( - reject: (reason?: any) => void, - message: string, - err?: Error - ): void { - this.logChannel.warn(message); - vscode.window.showErrorMessage(message); - if (err) { - this.logChannel.warn(err.message); - } - reject(err || new Error(message)); - } - - /** - * Handles the process exit event and resolves or rejects the promise. - * @param code The process exit code. - * @param result The formatted result. - * @param errorMessage The error message, if any. - * @param resolve The promise resolve function. - * @param reject The promise reject function. - */ - private handleExit( - code: number | null, - result: string, - errorMessage: string, - resolve: (value: string | PromiseLike) => void, - reject: (reason?: any) => void - ): void { - if (code && code !== 0) { - const error = `Failed with exit code: ${code}. ${errorMessage}`; - this.logChannel.error(error); - vscode.window.showErrorMessage(error); - reject(new Error(error)); - } else { - resolve(result); - } - } - - /** - * Handles errors by logging and displaying a message to the user. - * @param error The error object or message. - * @param userMessage The message to display to the user. - */ - private handleError(error: any, userMessage: string): void { - const errorMessage = - error instanceof Error ? error.message : "Unknown error occurred"; - this.logChannel.error(errorMessage); - vscode.window.showErrorMessage(`${userMessage}: ${errorMessage}`); - } - /** * Gets the executable path for HTML Beautifier based on the configuration. * @returns The path to the executable. diff --git a/src/formatter/htmlbeautifierProvider.ts b/src/formatter/htmlbeautifierProvider.ts index bb261f1..c488a84 100644 --- a/src/formatter/htmlbeautifierProvider.ts +++ b/src/formatter/htmlbeautifierProvider.ts @@ -1,5 +1,6 @@ import * as vscode from "vscode"; import HtmlBeautifier from "./htmlbeautifier"; +import micromatch from "micromatch"; export default class HtmlBeautifierProvider implements @@ -13,40 +14,30 @@ export default class HtmlBeautifierProvider } /** - * Provides formatting edits for the entire document - * @param {vscode.TextDocument} document - The document to be formatted - * @param {vscode.FormattingOptions} options - The formatting options - * @param {vscode.CancellationToken} token - The cancellation token - * @returns {vscode.ProviderResult} The formatting edits + * Provides formatting edits for the entire document. + * @param document - The document to be formatted. + * @param options - The formatting options. + * @param token - The cancellation token. + * @returns The formatting edits. */ public provideDocumentFormattingEdits( document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken ): vscode.ProviderResult { - return this.htmlbeautifier.format(document.getText()).then( - (result) => { - return [ - new vscode.TextEdit( - document.validateRange(new vscode.Range(0, 0, Infinity, Infinity)), - result - ), - ]; - }, - (err) => { - // will be handled in format - return []; - } - ); + const start = new vscode.Position(0, 0); // Start at the beginning of the document. + const end = document.lineAt(document.lineCount - 1).range.end; // End at the last line of the document. + const range = new vscode.Range(start, end); // Range for the entire document. + return this.formatDocument(document, range); // Format the entire document. } /** - * Provides formatting edits for a specific range within the document - * @param {vscode.TextDocument} document - The document to be formatted - * @param {vscode.Range} range - The range to be formatted - * @param {vscode.FormattingOptions} options - The formatting options - * @param {vscode.CancellationToken} token - The cancellation token - * @returns {vscode.ProviderResult} The formatting edits + * Provides formatting edits for a specific range within the document. + * @param document - The document to be formatted. + * @param range - The range to be formatted. + * @param options - The formatting options. + * @param token - The cancellation token. + * @returns The formatting edits. */ public provideDocumentRangeFormattingEdits( document: vscode.TextDocument, @@ -54,14 +45,46 @@ export default class HtmlBeautifierProvider options: vscode.FormattingOptions, token: vscode.CancellationToken ): vscode.ProviderResult { + return this.formatDocument(document, range); + } + + /** + * Formats the document or a specific range within the document. + * @param document - The document to be formatted. + * @param range - The range to be formatted. + * @returns The formatting edits. + */ + private formatDocument( + document: vscode.TextDocument, + range: vscode.Range + ): vscode.ProviderResult { + if (this.shouldIgnore(document)) { + this.htmlbeautifier.logChannel.info(`Ignoring ${document.fileName}`); + return []; + } + return this.htmlbeautifier.format(document.getText(range)).then( - (result) => { - return [new vscode.TextEdit(range, result)]; - }, - (err) => { - // will be handled in format + (result) => [new vscode.TextEdit(range, result)], + (error) => { + const errorMessage = + error instanceof Error ? error.message : String(error); + const shortFileName = document.fileName.split("/").pop(); + const fullMessage = `Error formatting ${shortFileName}: ${errorMessage}`; + this.htmlbeautifier.logChannel.error(fullMessage); + vscode.window.showErrorMessage(fullMessage); return []; } ); } + + /** + * Checks if the document should be ignored based on user-defined patterns. + * @param document - The document to be checked. + * @returns Whether the document should be ignored. + */ + private shouldIgnore(document: vscode.TextDocument): boolean { + const config = vscode.workspace.getConfiguration("vscode-erb-beautify"); + const ignorePatterns: string[] = config.get("ignoreFormatFilePatterns", []); + return micromatch.isMatch(document.fileName, ignorePatterns); + } } diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index a47cfe6..7a847ed 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -3,54 +3,58 @@ import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; -suite("ERB Formatter/Beautify tests", () => { - const sleep = (ms: number) => +suite("ERB Formatter/Beautify Tests", () => { + /** + * Sleeps for a given number of milliseconds. + * @param ms - Milliseconds to sleep. + * @returns A promise that resolves after the specified delay. + */ + const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); /** - * Resolve the test file path located in the `fixtures` directory. + * Resolves the test file path located in the `fixtures` directory. * @param filename - The name of the file. * @returns The full path of the test file. */ - function resolveTestFilePath(filename: string): string { - return path.resolve(__dirname, "../../", "src/test/fixtures/", filename); - } + const resolveTestFilePath = (filename: string): string => + path.resolve(__dirname, "../../", "src/test/fixtures/", filename); /** * Reads the content of a test file located in the `fixtures` directory. * @param filename - The name of the file to be read. * @returns The content of the file in UTF-8 encoding. */ - function readTestFile(filename: string): string { - return fs.readFileSync(resolveTestFilePath(filename), "utf-8"); - } + const readTestFile = (filename: string): string => + fs.readFileSync(resolveTestFilePath(filename), "utf-8"); /** * Changes a specific configuration value. * @param key - The configuration key. * @param value - The new value to set. + * @returns A promise that resolves after the configuration is updated. */ - async function changeConfig(key: string, value: any): Promise { + const changeConfig = async (key: string, value: any): Promise => { await vscode.workspace .getConfiguration() .update(key, value, vscode.ConfigurationTarget.Global); - } + }; /** * Formats a document and asserts its content against an expected formatted version. * @param initialFile - The initial unformatted file name. * @param expectedFile - The expected formatted file name. * @param formatCommand - The vscode command to execute for formatting. + * @returns A promise that resolves after the document is formatted and asserted. */ - async function formatAndAssert( + const formatAndAssert = async ( initialFile: string, expectedFile: string, formatCommand: string = "editor.action.formatDocument" - ): Promise { - const document = await vscode.workspace.openTextDocument({ - language: "erb", - content: readTestFile(initialFile), - }); + ): Promise => { + const document = await vscode.workspace.openTextDocument( + resolveTestFilePath(initialFile) + ); await vscode.window.showTextDocument(document); await sleep(1500); // Allow time for the extension to load. @@ -62,79 +66,103 @@ suite("ERB Formatter/Beautify tests", () => { await vscode.commands.executeCommand(formatCommand); await sleep(500); // Allow time for the formatting to occur. - assert.strictEqual(document.getText(), readTestFile(expectedFile)); - } + assert.strictEqual( + document.getText(), + readTestFile(expectedFile), + `Formatting did not produce the expected result for ${initialFile}` + ); + }; - test("formats whole document without bundler", async () => { - await changeConfig("vscode-erb-beautify.useBundler", false); + /** + * Runs a series of formatting tests with various configurations. + * @param useBundler - Whether to use bundler for formatting. + * @param formatSelection - Whether to format the selection instead of the whole document. + * @returns A promise that resolves after the tests are executed. + */ + const runFormattingTests = async ( + useBundler: boolean, + formatSelection = false + ) => { + const formatCommand = formatSelection + ? "editor.action.formatSelection" + : "editor.action.formatDocument"; + + await changeConfig("vscode-erb-beautify.useBundler", useBundler); await formatAndAssert( "sample_unformatted.html.erb", - "sample_formatted.html.erb" + "sample_formatted.html.erb", + formatCommand ); + }; + + test("Formats whole document without bundler", async () => { + await runFormattingTests(false); }); - test("formats whole document using bundler", async () => { - await changeConfig("vscode-erb-beautify.useBundler", true); - await formatAndAssert( - "sample_unformatted.html.erb", - "sample_formatted.html.erb" - ); + test("Formats whole document using bundler", async () => { + await runFormattingTests(true); }); - test("formats selection without bundler", async () => { - await changeConfig("vscode-erb-beautify.useBundler", false); - await formatAndAssert( - "sample_unformatted.html.erb", - "sample_formatted.html.erb", - "editor.action.formatSelection" - ); + test("Formats selection without bundler", async () => { + await runFormattingTests(false, true); }); - test("formats selection using bundler", async () => { - await changeConfig("vscode-erb-beautify.useBundler", true); - await formatAndAssert( - "sample_unformatted.html.erb", - "sample_formatted.html.erb", - "editor.action.formatSelection" - ); + test("Formats selection using bundler", async () => { + await runFormattingTests(true, true); }); - test("formats without encoding issue", async () => { + test("Formats without encoding issue", async () => { await formatAndAssert( "encoding_unformatted.html.erb", "encoding_formatted.html.erb" ); }); - test("Input ERB without final newline, insertFinalNewline=true", async () => { - await changeConfig("files.insertFinalNewline", true); + test("Formats ERB without final newline, insertFinalNewline=false", async () => { + await changeConfig("files.insertFinalNewline", false); await formatAndAssert( "without_final_newline.html.erb", - "with_final_newline.html.erb" + "without_final_newline.html.erb" ); }); - test("Input ERB with final newline, insertFinalNewline=true", async () => { + test("Formats ERB without final newline, insertFinalNewline=true", async () => { await changeConfig("files.insertFinalNewline", true); await formatAndAssert( - "with_final_newline.html.erb", + "without_final_newline.html.erb", "with_final_newline.html.erb" ); }); - test("Input ERB without final newline, insertFinalNewline=false", async () => { - await changeConfig("files.insertFinalNewline", false); + test("Formats ERB with final newline, insertFinalNewline=true", async () => { + await changeConfig("files.insertFinalNewline", true); await formatAndAssert( - "without_final_newline.html.erb", - "without_final_newline.html.erb" + "with_final_newline.html.erb", + "with_final_newline.html.erb" ); }); - test("Input ERB with final newline, insertFinalNewline=false", async () => { + test("Formats ERB with final newline, insertFinalNewline=false", async () => { await changeConfig("files.insertFinalNewline", false); await formatAndAssert( "with_final_newline.html.erb", "with_final_newline.html.erb" ); }); + + test("Ignores formatting for files matching ignore patterns", async () => { + await changeConfig("vscode-erb-beautify.ignoreFormatFilePatterns", [ + "**/*.text.erb", + ]); + + const initialFile = "ignored_file.text.erb"; + const initialContent = readTestFile(initialFile); + + await formatAndAssert(initialFile, initialFile); + assert.strictEqual( + readTestFile(initialFile), + initialContent, + "File content should remain unchanged when ignored" + ); + }); }); diff --git a/src/test/fixtures/ignored_file.text.erb b/src/test/fixtures/ignored_file.text.erb new file mode 100644 index 0000000..8a9cdfe --- /dev/null +++ b/src/test/fixtures/ignored_file.text.erb @@ -0,0 +1,3 @@ +<%- if account %> +* Account ID +<%- end %>