From 57c7361542a6e6862b4e90f7fb3e7ba59336299d Mon Sep 17 00:00:00 2001 From: senkenn Date: Mon, 14 Oct 2024 02:10:02 +0900 Subject: [PATCH 1/3] Refactor format implementation with paramTypes Fixes #110 --- .../test-workspace-rs/.sql-formatter.json | 5 ++- .../test-workspace-ts/.sql-formatter.json | 5 ++- vsce-test/test/suite-rs/extension.test.ts | 3 ++ vsce/src/commands/formatSql.ts | 35 ++----------------- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/vsce-test/test-workspace-rs/.sql-formatter.json b/vsce-test/test-workspace-rs/.sql-formatter.json index c761208..7b5280f 100644 --- a/vsce-test/test-workspace-rs/.sql-formatter.json +++ b/vsce-test/test-workspace-rs/.sql-formatter.json @@ -1,3 +1,6 @@ { - "keywordCase": "upper" + "keywordCase": "upper", + "paramTypes": { + "numbered": ["$"] + } } diff --git a/vsce-test/test-workspace-ts/.sql-formatter.json b/vsce-test/test-workspace-ts/.sql-formatter.json index c761208..7b5280f 100644 --- a/vsce-test/test-workspace-ts/.sql-formatter.json +++ b/vsce-test/test-workspace-ts/.sql-formatter.json @@ -1,3 +1,6 @@ { - "keywordCase": "upper" + "keywordCase": "upper", + "paramTypes": { + "numbered": ["$"] + } } diff --git a/vsce-test/test/suite-rs/extension.test.ts b/vsce-test/test/suite-rs/extension.test.ts index b665dce..57b0347 100644 --- a/vsce-test/test/suite-rs/extension.test.ts +++ b/vsce-test/test/suite-rs/extension.test.ts @@ -249,6 +249,9 @@ describe("Formatting Test", () => { const sqlFormatterConfigPath = path.resolve(wsPath, ".sql-formatter.json"); const sqlFormatterOptionsStr = JSON.stringify({ tabWidth: 4, + paramTypes: { + numbered: ["$"], + }, }); fs.writeFileSync(sqlFormatterConfigPath, sqlFormatterOptionsStr); diff --git a/vsce/src/commands/formatSql.ts b/vsce/src/commands/formatSql.ts index 218d5cc..6cbc9a1 100644 --- a/vsce/src/commands/formatSql.ts +++ b/vsce/src/commands/formatSql.ts @@ -30,7 +30,6 @@ async function formatSql(refresh: RefreshFunc): Promise { } const document = editor.document; const sqlNodes = await refresh(document); - const dummyPlaceHolder = '"SQLSURGE_DUMMY"'; // get sql-formatter config const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; @@ -121,44 +120,14 @@ async function formatSql(refresh: RefreshFunc): Promise { continue; } - // convert place holder to dummy if there are any place holders - const placeHolderRegExp = - document.languageId === "typescript" - ? /\$(\{.*\}|\d+)/g // ${1} or $1 - : document.languageId === "rust" - ? /(\$\d+|\?)/g - : undefined; - if (placeHolderRegExp === undefined) { - throw new Error("placeHolderRegExp is undefined"); - } - - const placeHolders = sqlNode.content.match(placeHolderRegExp); - if (placeHolders) { - sqlNode.content = sqlNode.content.replaceAll( - placeHolderRegExp, - dummyPlaceHolder, - ); - } - const isEnabledIndent = getWorkspaceConfig("formatSql.indent"); // get formatted content - const formattedContentWithDummy = format( + let formattedContent = format( sqlNode.content, - sqlFormatterOptions, + sqlFormatterOptions as FormatOptionsWithLanguage, ); - // reverse the place holders - let formattedContent = formattedContentWithDummy; - if (placeHolders) { - placeHolders.forEach((placeHolder, index) => { - formattedContent = formattedContent.replace( - dummyPlaceHolder, - placeHolder, - ); - }); - } - // add indent if config is enabled if (isEnabledIndent) { formattedContent = indentedContent( From 912cae1de71a06240fa6b1de6c2824a3c9a28861 Mon Sep 17 00:00:00 2001 From: senkenn Date: Mon, 14 Oct 2024 02:20:18 +0900 Subject: [PATCH 2/3] refactor: streamline SQL formatting logic and enhance configuration handling --- vsce/src/commands/formatSql.ts | 262 ++++++++++++++++++--------------- 1 file changed, 146 insertions(+), 116 deletions(-) diff --git a/vsce/src/commands/formatSql.ts b/vsce/src/commands/formatSql.ts index 6cbc9a1..906108a 100644 --- a/vsce/src/commands/formatSql.ts +++ b/vsce/src/commands/formatSql.ts @@ -1,10 +1,8 @@ -import type { SqlNode } from "../interface"; - import { type FormatOptionsWithLanguage, format } from "sql-formatter"; import * as ts from "typescript"; import * as vscode from "vscode"; - import { getWorkspaceConfig } from "../extConfig"; +import type { SqlNode } from "../interface"; import { createLogger } from "../outputChannel"; const sqlFormatterConfigFileName = ".sql-formatter.json"; @@ -31,125 +29,17 @@ async function formatSql(refresh: RefreshFunc): Promise { const document = editor.document; const sqlNodes = await refresh(document); - // get sql-formatter config - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (!workspaceFolder) { - logger.error("[commandFormatSqlProvider]", "Not open workspace"); - return; - } - const sqlFormatterConfigUri = vscode.Uri.joinPath( - workspaceFolder.uri, - sqlFormatterConfigFileName, - ); - let sqlFormatterOptionsBinary: Uint8Array | undefined = undefined; - try { + const sqlFormatterOptions = await getSqlFormatterOptions(); + if (!sqlFormatterOptions) { logger.info( "[commandFormatSqlProvider]", - "Find & Reading sql-formatter config file.", - ); - sqlFormatterOptionsBinary = await vscode.workspace.fs.readFile( - sqlFormatterConfigUri, - ); - } catch (error) { - logger.warn( - "[commandFormatSqlProvider]", - "Failed to read sql-formatter config file.", - error, - ); - logger.info( - "[commandFormatSqlProvider]", - "Use default sql-formatter config.", + "Using default sql-formatter config.", ); } - const sqlFormatterOptions = - sqlFormatterOptionsBinary && - (JSON.parse( - sqlFormatterOptionsBinary.toString(), - ) as FormatOptionsWithLanguage); - // edit document with formatted content editor.edit((editBuilder) => { for (const sqlNode of sqlNodes) { - logger.debug("[formatSql]", "Formatting", sqlNode); - - // skip empty content - if (sqlNode.content.match(/^\s*$/)) { - logger.debug( - "[formatSql]", - "Skip formatting for empty content", - sqlNode, - ); - continue; - } - - // get prefix and suffix of space or new line - const prefix = - sqlNode.content - .match(/^(\s*)/)?.[0] - .replace(/ +$/g, "") ?? // remove indent space - ""; - const suffix = sqlNode.content.match(/(\s*)$/)?.[0] ?? ""; - - // skip typescript && "" or '' string(1 line) - const sourceText = document.getText(); - const sourceFile = ts.createSourceFile( - "unusedFileName", - sourceText, - ts.ScriptTarget.Latest, // TODO: #74 re-consider this it should be the same as the vscode lsp or project tsconfig.json - ); - const startPosition = - sourceFile.getPositionOfLineAndCharacter( - sqlNode.code_range.start.line, - sqlNode.code_range.start.character, - ) - prefix.length; - const endPosition = - sourceFile.getPositionOfLineAndCharacter( - sqlNode.code_range.end.line, - sqlNode.code_range.end.character, - ) + suffix.length; - if ( - document.languageId === "typescript" && - sourceText[startPosition - 1]?.match(/^["']$/) && - sourceText[endPosition]?.match(/^["']$/) - ) { - logger.debug( - "[formatSql]", - "Skip formatting for typescript string 1 line", - sqlNode, - ); - continue; - } - - const isEnabledIndent = getWorkspaceConfig("formatSql.indent"); - - // get formatted content - let formattedContent = format( - sqlNode.content, - sqlFormatterOptions as FormatOptionsWithLanguage, - ); - - // add indent if config is enabled - if (isEnabledIndent) { - formattedContent = indentedContent( - formattedContent, - sqlNode.method_line, - ); - } - - // replace with formatted content - editBuilder.replace( - new vscode.Range( - new vscode.Position( - sqlNode.code_range.start.line, - sqlNode.code_range.start.character, - ), - new vscode.Position( - sqlNode.code_range.end.line, - sqlNode.code_range.end.character, - ), - ), - prefix + formattedContent + suffix, - ); + formatSqlNode(sqlNode, document, editBuilder, sqlFormatterOptions); } }); @@ -160,6 +50,146 @@ async function formatSql(refresh: RefreshFunc): Promise { } } +async function getSqlFormatterOptions(): Promise< + FormatOptionsWithLanguage | undefined +> { + const logger = createLogger(); + + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + logger.error("[commandFormatSqlProvider]", "No open workspace"); + return undefined; + } + + const sqlFormatterConfigUri = vscode.Uri.joinPath( + workspaceFolder.uri, + sqlFormatterConfigFileName, + ); + try { + logger.info( + "[commandFormatSqlProvider]", + "Reading sql-formatter config file.", + ); + const sqlFormatterOptionsBinary = await vscode.workspace.fs.readFile( + sqlFormatterConfigUri, + ); + return JSON.parse( + sqlFormatterOptionsBinary.toString(), + ) as FormatOptionsWithLanguage; + } catch (error) { + logger.warn( + "[commandFormatSqlProvider]", + "Failed to read sql-formatter config file.", + error, + ); + return undefined; + } +} + +function formatSqlNode( + sqlNode: SqlNode & { vFileName: string }, + document: vscode.TextDocument, + editBuilder: vscode.TextEditorEdit, + sqlFormatterOptions: FormatOptionsWithLanguage | undefined, +): void { + const logger = createLogger(); + logger.debug("[formatSql]", "Formatting", sqlNode); + + if (sqlNode.content.match(/^\s*$/)) { + logger.debug("[formatSql]", "Skip formatting for empty content", sqlNode); + return; + } + + const { prefix, suffix } = getPrefixAndSuffix(sqlNode.content); + const { startPosition, endPosition } = getPositions( + sqlNode, + document, + prefix, + suffix, + ); + + if (shouldSkipFormatting(document, startPosition, endPosition)) { + logger.debug( + "[formatSql]", + "Skip formatting for typescript string 1 line", + sqlNode, + ); + return; + } + + const isEnabledIndent = getWorkspaceConfig("formatSql.indent"); + let formattedContent = format(sqlNode.content, sqlFormatterOptions); + + if (isEnabledIndent) { + formattedContent = indentedContent(formattedContent, sqlNode.method_line); + } + + editBuilder.replace( + new vscode.Range( + new vscode.Position( + sqlNode.code_range.start.line, + sqlNode.code_range.start.character, + ), + new vscode.Position( + sqlNode.code_range.end.line, + sqlNode.code_range.end.character, + ), + ), + prefix + formattedContent + suffix, + ); +} + +function getPrefixAndSuffix(content: string): { + prefix: string; + suffix: string; +} { + const prefix = content.match(/^(\s*)/)?.[0].replace(/ +$/g, "") ?? ""; + const suffix = content.match(/(\s*)$/)?.[0] ?? ""; + return { prefix, suffix }; +} + +function getPositions( + sqlNode: SqlNode, + document: vscode.TextDocument, + prefix: string, + suffix: string, +): { startPosition: number; endPosition: number } { + const sourceText = document.getText(); + const sourceFile = ts.createSourceFile( + "unusedFileName", + sourceText, + ts.ScriptTarget.Latest, // TODO: #74 re-consider this it should be the same as the vscode lsp or project tsconfig.json + ); + const startPosition = + sourceFile.getPositionOfLineAndCharacter( + sqlNode.code_range.start.line, + sqlNode.code_range.start.character, + ) - prefix.length; + const endPosition = + sourceFile.getPositionOfLineAndCharacter( + sqlNode.code_range.end.line, + sqlNode.code_range.end.character, + ) + suffix.length; + return { startPosition, endPosition }; +} + +function shouldSkipFormatting( + document: vscode.TextDocument, + startPosition: number, + endPosition: number, +): boolean { + const sourceText = document.getText(); + const startMatch = sourceText[startPosition - 1]?.match(/^["']$/); + const endMatch = sourceText[endPosition]?.match(/^["']$/); + return ( + document.languageId === "typescript" && + startMatch !== undefined && + startMatch !== null && + endMatch !== undefined && + endMatch !== null + ); +} + function indentedContent( content: string, methodLine: SqlNode["method_line"], @@ -172,7 +202,7 @@ function indentedContent( if (typeof tabSize === "string") { logger.warn( "[indentedContent]", - `tabSize is ${tabSize}. Use default tabSize: ${defaultTabSize}`, + `tabSize is ${tabSize}. Using default tabSize: ${defaultTabSize}`, ); tabSize = defaultTabSize; } From 9bbe9aaf863aee2fb6845748da54e9ee8f0c08e2 Mon Sep 17 00:00:00 2001 From: senkenn Date: Mon, 14 Oct 2024 02:26:15 +0900 Subject: [PATCH 3/3] refactor: update SQL formatter options to read from configuration file --- vsce-test/test/suite-rs/extension.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/vsce-test/test/suite-rs/extension.test.ts b/vsce-test/test/suite-rs/extension.test.ts index 57b0347..4fb5e6e 100644 --- a/vsce-test/test/suite-rs/extension.test.ts +++ b/vsce-test/test/suite-rs/extension.test.ts @@ -247,13 +247,17 @@ describe("Formatting Test", () => { .getConfiguration("sqlsurge", vscode.workspace.workspaceFolders?.[0]?.uri) .update("formatSql.indent", true); const sqlFormatterConfigPath = path.resolve(wsPath, ".sql-formatter.json"); - const sqlFormatterOptionsStr = JSON.stringify({ + const sqlFormatterOptions = JSON.parse( + fs.readFileSync(sqlFormatterConfigPath, "utf8"), + ); + const newSqlFormatterOptions = { + ...sqlFormatterOptions, tabWidth: 4, - paramTypes: { - numbered: ["$"], - }, - }); - fs.writeFileSync(sqlFormatterConfigPath, sqlFormatterOptionsStr); + }; + fs.writeFileSync( + sqlFormatterConfigPath, + JSON.stringify(newSqlFormatterOptions), + ); // execute command await vscode.commands.executeCommand("sqlsurge.formatSql");