diff --git a/extensions/mssql/l10n/bundle.l10n.json b/extensions/mssql/l10n/bundle.l10n.json index 6bd607fad2..d05673fe14 100644 --- a/extensions/mssql/l10n/bundle.l10n.json +++ b/extensions/mssql/l10n/bundle.l10n.json @@ -2150,6 +2150,8 @@ }, "View known issues": "View known issues", "Service installation failed.": "Service installation failed.", + "SQL formatting could not be completed because the T-SQL could not be fully parsed. If you believe the syntax is valid, please send feedback.": "SQL formatting could not be completed because the T-SQL could not be fully parsed. If you believe the syntax is valid, please send feedback.", + "Send Feedback": "Send Feedback", "Azure sign in failed.": "Azure sign in failed.", "Select subscriptions": "Select subscriptions", "Error loading Azure subscriptions.": "Error loading Azure subscriptions.", diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index eb1f273d7b..ed851c828f 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -2428,9 +2428,15 @@ "default": true, "scope": "window" }, + "mssql.format.showParseErrorNotification": { + "type": "boolean", + "description": "%mssql.format.showParseErrorNotification%", + "default": true, + "scope": "window" + }, "mssql.format.options.sqlVersion": { "type": "string", - "description": "%mssql.format.options.sqlVersion%", + "markdownDescription": "%mssql.format.options.sqlVersion%", "default": "sql170", "enum": [ "sql80", @@ -2448,7 +2454,7 @@ }, "mssql.format.options.sqlEngineType": { "type": "string", - "description": "%mssql.format.options.sqlEngineType%", + "markdownDescription": "%mssql.format.options.sqlEngineType%", "default": "all", "enum": [ "all", @@ -2459,43 +2465,43 @@ }, "mssql.format.options.alignClauseBodies": { "type": "boolean", - "description": "%mssql.format.options.alignClauseBodies%", + "markdownDescription": "%mssql.format.options.alignClauseBodies%", "default": true, "scope": "window" }, "mssql.format.options.alignColumnDefinitionFields": { "type": "boolean", - "description": "%mssql.format.options.alignColumnDefinitionFields%", + "markdownDescription": "%mssql.format.options.alignColumnDefinitionFields%", "default": true, "scope": "window" }, "mssql.format.options.alignSetClauseItem": { "type": "boolean", - "description": "%mssql.format.options.alignSetClauseItem%", + "markdownDescription": "%mssql.format.options.alignSetClauseItem%", "default": true, "scope": "window" }, "mssql.format.options.allowExternalLanguagePaths": { "type": "boolean", - "description": "%mssql.format.options.allowExternalLanguagePaths%", + "markdownDescription": "%mssql.format.options.allowExternalLanguagePaths%", "default": true, "scope": "window" }, "mssql.format.options.allowExternalLibraryPaths": { "type": "boolean", - "description": "%mssql.format.options.allowExternalLibraryPaths%", + "markdownDescription": "%mssql.format.options.allowExternalLibraryPaths%", "default": true, "scope": "window" }, "mssql.format.options.asKeywordOnOwnLine": { "type": "boolean", - "description": "%mssql.format.options.asKeywordOnOwnLine%", + "markdownDescription": "%mssql.format.options.asKeywordOnOwnLine%", "default": true, "scope": "window" }, "mssql.format.options.keywordCasing": { "type": "string", - "description": "%mssql.format.options.keywordCasing%", + "markdownDescription": "%mssql.format.options.keywordCasing%", "default": "uppercase", "enum": [ "uppercase", @@ -2506,139 +2512,139 @@ }, "mssql.format.options.preserveComments": { "type": "boolean", - "description": "%mssql.format.options.preserveComments%", + "markdownDescription": "%mssql.format.options.preserveComments%", "default": true, "scope": "window" }, "mssql.format.options.indentSetClause": { "type": "boolean", - "description": "%mssql.format.options.indentSetClause%", + "markdownDescription": "%mssql.format.options.indentSetClause%", "default": false, "scope": "window" }, "mssql.format.options.indentViewBody": { "type": "boolean", - "description": "%mssql.format.options.indentViewBody%", + "markdownDescription": "%mssql.format.options.indentViewBody%", "default": false, "scope": "window" }, "mssql.format.options.multilineInsertSourcesList": { "type": "boolean", - "description": "%mssql.format.options.multilineInsertSourcesList%", + "markdownDescription": "%mssql.format.options.multilineInsertSourcesList%", "default": true, "scope": "window" }, "mssql.format.options.multilineInsertTargetsList": { "type": "boolean", - "description": "%mssql.format.options.multilineInsertTargetsList%", + "markdownDescription": "%mssql.format.options.multilineInsertTargetsList%", "default": true, "scope": "window" }, "mssql.format.options.multilineSelectElementsList": { "type": "boolean", - "description": "%mssql.format.options.multilineSelectElementsList%", + "markdownDescription": "%mssql.format.options.multilineSelectElementsList%", "default": true, "scope": "window" }, "mssql.format.options.multilineSetClauseItems": { "type": "boolean", - "description": "%mssql.format.options.multilineSetClauseItems%", + "markdownDescription": "%mssql.format.options.multilineSetClauseItems%", "default": true, "scope": "window" }, "mssql.format.options.multilineViewColumnsList": { "type": "boolean", - "description": "%mssql.format.options.multilineViewColumnsList%", + "markdownDescription": "%mssql.format.options.multilineViewColumnsList%", "default": true, "scope": "window" }, "mssql.format.options.multilineWherePredicatesList": { "type": "boolean", - "description": "%mssql.format.options.multilineWherePredicatesList%", + "markdownDescription": "%mssql.format.options.multilineWherePredicatesList%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeCloseParenthesisInMultilineList": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeCloseParenthesisInMultilineList%", + "markdownDescription": "%mssql.format.options.newLineBeforeCloseParenthesisInMultilineList%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeFromClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeFromClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeFromClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeGroupByClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeGroupByClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeGroupByClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeHavingClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeHavingClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeHavingClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeJoinClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeJoinClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeJoinClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeOffsetClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeOffsetClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeOffsetClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeOpenParenthesisInMultilineList": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeOpenParenthesisInMultilineList%", + "markdownDescription": "%mssql.format.options.newLineBeforeOpenParenthesisInMultilineList%", "default": false, "scope": "window" }, "mssql.format.options.newLineBeforeOrderByClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeOrderByClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeOrderByClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeOutputClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeOutputClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeOutputClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeWhereClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeWhereClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeWhereClause%", "default": true, "scope": "window" }, "mssql.format.options.newLineBeforeWindowClause": { "type": "boolean", - "description": "%mssql.format.options.newLineBeforeWindowClause%", + "markdownDescription": "%mssql.format.options.newLineBeforeWindowClause%", "default": true, "scope": "window" }, "mssql.format.options.newlineFormattedCheckConstraint": { "type": "boolean", - "description": "%mssql.format.options.newlineFormattedCheckConstraint%", + "markdownDescription": "%mssql.format.options.newlineFormattedCheckConstraint%", "default": false, "scope": "window" }, "mssql.format.options.newLineFormattedIndexDefinition": { "type": "boolean", - "description": "%mssql.format.options.newLineFormattedIndexDefinition%", + "markdownDescription": "%mssql.format.options.newLineFormattedIndexDefinition%", "default": false, "scope": "window" }, "mssql.format.options.numNewlinesAfterStatement": { "type": "integer", - "description": "%mssql.format.options.numNewlinesAfterStatement%", + "markdownDescription": "%mssql.format.options.numNewlinesAfterStatement%", "default": 1, "minimum": 0, "maximum": 5, @@ -2646,13 +2652,13 @@ }, "mssql.format.options.spaceBetweenDataTypeAndParameters": { "type": "boolean", - "description": "%mssql.format.options.spaceBetweenDataTypeAndParameters%", + "markdownDescription": "%mssql.format.options.spaceBetweenDataTypeAndParameters%", "default": true, "scope": "window" }, "mssql.format.options.spaceBetweenParametersInDataType": { "type": "boolean", - "description": "%mssql.format.options.spaceBetweenParametersInDataType%", + "markdownDescription": "%mssql.format.options.spaceBetweenParametersInDataType%", "default": true, "scope": "window" }, diff --git a/extensions/mssql/package.nls.json b/extensions/mssql/package.nls.json index 697d910d8f..d800d7abe7 100644 --- a/extensions/mssql/package.nls.json +++ b/extensions/mssql/package.nls.json @@ -142,46 +142,47 @@ "mssql.showBatchTime": "[Optional] Should execution time be shown for individual batches", "mssql.splitPaneSelection": "[Optional] Configuration options for which column new result panes should open in", "mssql.preventAutoExecuteScript": "Prevent automatic execution of scripts (e.g., 'Select Top 1000'). When enabled, scripts will not be automatically executed upon generation.", - "mssql.format.alignColumnDefinitionsInColumns": "Legacy formatter only: Align column definitions. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting.", - "mssql.format.enablePreviewFormatter": "Use the new SQL formatter. Disable this setting to temporarily use the legacy formatter.", - "mssql.format.options.sqlVersion": "T-SQL version used to parse and generate formatted scripts.", - "mssql.format.options.sqlEngineType": "SQL engine type used to parse and generate formatted scripts.", - "mssql.format.options.alignClauseBodies": "Align the bodies of FROM, WHERE, GROUP BY, and similar clauses.", - "mssql.format.options.alignColumnDefinitionFields": "Align column-definition fields, such as names, data types, and constraints.", - "mssql.format.options.alignSetClauseItem": "Align SET clause items in UPDATE statements.", - "mssql.format.options.allowExternalLanguagePaths": "Allow external language content to use file paths.", - "mssql.format.options.allowExternalLibraryPaths": "Allow external library content to use file paths.", - "mssql.format.options.asKeywordOnOwnLine": "Place AS on its own line.", - "mssql.format.options.keywordCasing": "Keyword casing style.", - "mssql.format.options.preserveComments": "Preserve comments during formatting.", - "mssql.format.options.indentSetClause": "Indent SET clauses in UPDATE statements.", - "mssql.format.options.indentViewBody": "Indent VIEW bodies.", - "mssql.format.options.multilineInsertSourcesList": "Format INSERT sources as multiline lists.", - "mssql.format.options.multilineInsertTargetsList": "Format INSERT columns as multiline lists.", - "mssql.format.options.multilineSelectElementsList": "Format SELECT columns as multiline lists.", - "mssql.format.options.multilineSetClauseItems": "Format SET clause items as multiline lists.", - "mssql.format.options.multilineViewColumnsList": "Format VIEW columns as multiline lists.", - "mssql.format.options.multilineWherePredicatesList": "Format WHERE predicates as multiline lists.", - "mssql.format.options.newLineBeforeCloseParenthesisInMultilineList": "Place a newline before the closing parenthesis of a multiline list.", - "mssql.format.options.newLineBeforeFromClause": "Place a newline before the FROM clause.", - "mssql.format.options.newLineBeforeGroupByClause": "Place a newline before the GROUP BY clause.", - "mssql.format.options.newLineBeforeHavingClause": "Place a newline before the HAVING clause.", - "mssql.format.options.newLineBeforeJoinClause": "Place a newline before JOIN clauses.", - "mssql.format.options.newLineBeforeOffsetClause": "Place a newline before the OFFSET clause.", - "mssql.format.options.newLineBeforeOpenParenthesisInMultilineList": "Place a newline before the opening parenthesis of a multiline list.", - "mssql.format.options.newLineBeforeOrderByClause": "Place a newline before the ORDER BY clause.", - "mssql.format.options.newLineBeforeOutputClause": "Place a newline before the OUTPUT clause.", - "mssql.format.options.newLineBeforeWhereClause": "Place a newline before the WHERE clause.", - "mssql.format.options.newLineBeforeWindowClause": "Place a newline before the WINDOW clause.", - "mssql.format.options.newlineFormattedCheckConstraint": "Place the CHECK clause of a constraint on its own line.", - "mssql.format.options.newLineFormattedIndexDefinition": "Place UNIQUE, INCLUDE, and WHERE portions of inline index definitions on separate lines.", - "mssql.format.options.numNewlinesAfterStatement": "Number of line breaks after each statement, from 0 through 5.", - "mssql.format.options.spaceBetweenDataTypeAndParameters": "Insert a space between a data type and its parentheses, for example VARCHAR (255).", - "mssql.format.options.spaceBetweenParametersInDataType": "Insert spaces between parameters in a data type, for example DECIMAL (10, 2).", - "mssql.format.datatypeCasing": "Legacy formatter only: Control data type casing. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting.", - "mssql.format.keywordCasing": "Legacy formatter only: Control keyword and IntelliSense casing. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting.", - "mssql.format.placeCommasBeforeNextStatement": "Legacy formatter only: Place commas at the beginning instead of the end of list items. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting.", - "mssql.format.placeSelectStatementReferencesOnNewLine": "Legacy formatter only: Place SELECT statement object references on separate lines. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting.", + "mssql.format.alignColumnDefinitionsInColumns": "**Used when Preview Formatter is disabled.** Should column definitions be aligned?", + "mssql.format.enablePreviewFormatter": "Use the preview SQL formatter. Disable this setting to temporarily use the existing formatter.", + "mssql.format.showParseErrorNotification": "Show a notification when Preview Formatter cannot fully parse the T-SQL being formatted.", + "mssql.format.options.sqlVersion": "**Used when Preview Formatter is enabled.** T-SQL version used to parse and generate formatted scripts.", + "mssql.format.options.sqlEngineType": "**Used when Preview Formatter is enabled.** SQL engine type used to parse and generate formatted scripts.", + "mssql.format.options.alignClauseBodies": "**Used when Preview Formatter is enabled.** Align the bodies of FROM, WHERE, GROUP BY, and similar clauses.", + "mssql.format.options.alignColumnDefinitionFields": "**Used when Preview Formatter is enabled.** Align column-definition fields, such as names, data types, and constraints.", + "mssql.format.options.alignSetClauseItem": "**Used when Preview Formatter is enabled.** Align SET clause items in UPDATE statements.", + "mssql.format.options.allowExternalLanguagePaths": "**Used when Preview Formatter is enabled.** Allow external language content to use file paths.", + "mssql.format.options.allowExternalLibraryPaths": "**Used when Preview Formatter is enabled.** Allow external library content to use file paths.", + "mssql.format.options.asKeywordOnOwnLine": "**Used when Preview Formatter is enabled.** Place AS on its own line.", + "mssql.format.options.keywordCasing": "**Used when Preview Formatter is enabled.** Keyword casing style.", + "mssql.format.options.preserveComments": "**Used when Preview Formatter is enabled.** Preserve comments during formatting.", + "mssql.format.options.indentSetClause": "**Used when Preview Formatter is enabled.** Indent SET clauses in UPDATE statements.", + "mssql.format.options.indentViewBody": "**Used when Preview Formatter is enabled.** Indent VIEW bodies.", + "mssql.format.options.multilineInsertSourcesList": "**Used when Preview Formatter is enabled.** Format INSERT sources as multiline lists.", + "mssql.format.options.multilineInsertTargetsList": "**Used when Preview Formatter is enabled.** Format INSERT columns as multiline lists.", + "mssql.format.options.multilineSelectElementsList": "**Used when Preview Formatter is enabled.** Format SELECT columns as multiline lists.", + "mssql.format.options.multilineSetClauseItems": "**Used when Preview Formatter is enabled.** Format SET clause items as multiline lists.", + "mssql.format.options.multilineViewColumnsList": "**Used when Preview Formatter is enabled.** Format VIEW columns as multiline lists.", + "mssql.format.options.multilineWherePredicatesList": "**Used when Preview Formatter is enabled.** Format WHERE predicates as multiline lists.", + "mssql.format.options.newLineBeforeCloseParenthesisInMultilineList": "**Used when Preview Formatter is enabled.** Place a newline before the closing parenthesis of a multiline list.", + "mssql.format.options.newLineBeforeFromClause": "**Used when Preview Formatter is enabled.** Place a newline before the FROM clause.", + "mssql.format.options.newLineBeforeGroupByClause": "**Used when Preview Formatter is enabled.** Place a newline before the GROUP BY clause.", + "mssql.format.options.newLineBeforeHavingClause": "**Used when Preview Formatter is enabled.** Place a newline before the HAVING clause.", + "mssql.format.options.newLineBeforeJoinClause": "**Used when Preview Formatter is enabled.** Place a newline before JOIN clauses.", + "mssql.format.options.newLineBeforeOffsetClause": "**Used when Preview Formatter is enabled.** Place a newline before the OFFSET clause.", + "mssql.format.options.newLineBeforeOpenParenthesisInMultilineList": "**Used when Preview Formatter is enabled.** Place a newline before the opening parenthesis of a multiline list.", + "mssql.format.options.newLineBeforeOrderByClause": "**Used when Preview Formatter is enabled.** Place a newline before the ORDER BY clause.", + "mssql.format.options.newLineBeforeOutputClause": "**Used when Preview Formatter is enabled.** Place a newline before the OUTPUT clause.", + "mssql.format.options.newLineBeforeWhereClause": "**Used when Preview Formatter is enabled.** Place a newline before the WHERE clause.", + "mssql.format.options.newLineBeforeWindowClause": "**Used when Preview Formatter is enabled.** Place a newline before the WINDOW clause.", + "mssql.format.options.newlineFormattedCheckConstraint": "**Used when Preview Formatter is enabled.** Place the CHECK clause of a constraint on its own line.", + "mssql.format.options.newLineFormattedIndexDefinition": "**Used when Preview Formatter is enabled.** Place UNIQUE, INCLUDE, and WHERE portions of inline index definitions on separate lines.", + "mssql.format.options.numNewlinesAfterStatement": "**Used when Preview Formatter is enabled.** Number of line breaks after each statement, from 0 through 5.", + "mssql.format.options.spaceBetweenDataTypeAndParameters": "**Used when Preview Formatter is enabled.** Insert a space between a data type and its parentheses, for example `VARCHAR (255)`.", + "mssql.format.options.spaceBetweenParametersInDataType": "**Used when Preview Formatter is enabled.** Insert spaces between parameters in a data type, for example `DECIMAL (10, 2)`.", + "mssql.format.datatypeCasing": "**Used when Preview Formatter is disabled.** Should data types be formatted as UPPERCASE, lowercase, or none (not formatted)", + "mssql.format.keywordCasing": "**Used when Preview Formatter is disabled.** Should keywords be formatted as UPPERCASE, lowercase, or none (not formatted)", + "mssql.format.placeCommasBeforeNextStatement": "**Used when Preview Formatter is disabled.** Should commas be placed at the beginning of each statement in a list, for example `, mycolumn2`, instead of at the end, for example `mycolumn1,`?", + "mssql.format.placeSelectStatementReferencesOnNewLine": "**Used when Preview Formatter is disabled.** Should references in a SELECT statement be split into separate lines? For `SELECT C1, C2 FROM T1`, both C1 and C2 will be on separate lines.", "mssql.resultsGrid.autoSizeColumnsMode": "Choose how result grid columns are automatically sized (headers + data, data only, or disable auto sizing).", "mssql.resultsGrid.useHeaderAndDataEnumDescription": "Use column headers and data to size columns automatically", "mssql.resultsGrid.useDataOnlyEnumDescription": "Use only row data to size columns automatically", diff --git a/extensions/mssql/src/configurations/config.ts b/extensions/mssql/src/configurations/config.ts index 47f11900f1..614d6afcf1 100644 --- a/extensions/mssql/src/configurations/config.ts +++ b/extensions/mssql/src/configurations/config.ts @@ -7,7 +7,7 @@ export const config = { service: { downloadUrl: "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - version: "6.0.20260722.6", + version: "6.0.20260724.3", downloadFileNames: { Windows_64: "win-x64-net10.0.zip", Windows_ARM64: "win-arm64-net10.0.zip", diff --git a/extensions/mssql/src/constants/constants.ts b/extensions/mssql/src/constants/constants.ts index 69b9612d23..73e423a824 100644 --- a/extensions/mssql/src/constants/constants.ts +++ b/extensions/mssql/src/constants/constants.ts @@ -226,6 +226,7 @@ export const contentProviderMinFile = "dist/js/app.min.js"; export const timeToWaitForLanguageModeChange = 10000.0; export const gettingStartedGuideLink = "https://aka.ms/mssql-getting-started"; export const changelogLink = "https://aka.ms/vscode-mssql-changes"; +export const feedbackUrl = "https://aka.ms/vscode-mssql-bug"; export const encryptionBlogLink = "https://aka.ms/vscodemssql-connection"; export const integratedAuthHelpLink = "https://aka.ms/vscode-mssql-integratedauth"; export const createDatabaseHelpLink = diff --git a/extensions/mssql/src/constants/locConstants.ts b/extensions/mssql/src/constants/locConstants.ts index ed4d96d672..9e6fcd8e49 100644 --- a/extensions/mssql/src/constants/locConstants.ts +++ b/extensions/mssql/src/constants/locConstants.ts @@ -3923,6 +3923,13 @@ export class ServiceClient { public static installFailedStatusText = l10n.t("Service installation failed."); } +export class Formatter { + public static parseError = l10n.t( + "SQL formatting could not be completed because the T-SQL could not be fully parsed. If you believe the syntax is valid, please send feedback.", + ); + public static sendFeedback = l10n.t("Send Feedback"); +} + export const azureSignInFailed = l10n.t("Azure sign in failed."); export const selectSubscriptions = l10n.t("Select subscriptions"); diff --git a/extensions/mssql/src/languageservice/serviceclient.ts b/extensions/mssql/src/languageservice/serviceclient.ts index 219ae46925..6b41fd7016 100644 --- a/extensions/mssql/src/languageservice/serviceclient.ts +++ b/extensions/mssql/src/languageservice/serviceclient.ts @@ -28,7 +28,7 @@ import DownloadHelper from "./downloadHelper"; import ExtConfig from "../configurations/extConfig"; import DotnetRuntimeProvider from "./dotnetRuntimeProvider"; import { PlatformInformation, Runtime } from "../models/platform"; -import { ServiceClient } from "../constants/locConstants"; +import { Common, Formatter, ServiceClient } from "../constants/locConstants"; import { ServerStatusView } from "./serverStatus"; import StatusView from "../views/statusView"; import * as LanguageServiceContracts from "../models/contracts/languageService"; @@ -137,6 +137,7 @@ export default class SqlToolsServiceClient { // VS Code Language Client private _client: LanguageClient = undefined; private _resourceClient: LanguageClient = undefined; + private _formattingFailurePromptOpen = false; // getter method for the Language Client private get client(): LanguageClient { @@ -455,6 +456,10 @@ export default class SqlToolsServiceClient { LanguageServiceContracts.SqlToolsServiceTelemetryNotification.type, this.handleSqlToolsServiceTelemetryNotification(), ); + client.onNotification( + LanguageServiceContracts.FormattingFailedNotification.type, + this.handleFormattingFailedNotification(), + ); return client; } @@ -508,6 +513,67 @@ export default class SqlToolsServiceClient { }; } + /** + * Public for testing purposes only. + */ + public handleFormattingFailedNotification(): NotificationHandler { + return (event: LanguageServiceContracts.FormattingFailedParams): void => { + void this.showFormattingFailedNotification(event).catch((error) => { + logger.error( + "Failed to handle formatting failure notification.", + getErrorMessage(error), + ); + }); + }; + } + + /** + * Public for testing purposes only. + */ + public async showFormattingFailedNotification( + event: LanguageServiceContracts.FormattingFailedParams, + ): Promise { + const configuration = vscode.workspace.getConfiguration( + Constants.extensionConfigSectionName, + ); + if ( + event.reason !== "ParseError" || + this._formattingFailurePromptOpen || + !configuration.get("format.showParseErrorNotification", true) + ) { + return; + } + + this._formattingFailurePromptOpen = true; + try { + const action = await vscode.window.showWarningMessage( + Formatter.parseError, + Formatter.sendFeedback, + Common.dontShowAgain, + ); + + if (action === Formatter.sendFeedback) { + sendActionEvent( + TelemetryViews.QueryEditor, + TelemetryActions.FormatterParseErrorSendFeedback, + ); + await vscode.env.openExternal(vscode.Uri.parse(Constants.feedbackUrl)); + } else if (action === Common.dontShowAgain) { + sendActionEvent( + TelemetryViews.QueryEditor, + TelemetryActions.FormatterParseErrorDontShowAgain, + ); + await configuration.update( + "format.showParseErrorNotification", + false, + vscode.ConfigurationTarget.Global, + ); + } + } finally { + this._formattingFailurePromptOpen = false; + } + } + private sendServiceLaunchTelemetry( launchType: ServiceLaunchType, serviceRuntime: Runtime, diff --git a/extensions/mssql/src/models/contracts/languageService.ts b/extensions/mssql/src/models/contracts/languageService.ts index 79794e7399..1077417f1d 100644 --- a/extensions/mssql/src/models/contracts/languageService.ts +++ b/extensions/mssql/src/models/contracts/languageService.ts @@ -25,6 +25,26 @@ export namespace SqlToolsServiceTelemetryNotification { // ------------------------------- ------------------------- +// ------------------------------- < Formatting Failed Event > ------------------------------------ + +export type FormattingFailureReason = "ParseError"; +export type FormattingRequestType = "Document" | "Range"; + +export interface FormattingFailedParams { + ownerUri: string; + formatType: FormattingRequestType; + reason: FormattingFailureReason; + parseErrorCount: number; +} + +export namespace FormattingFailedNotification { + export const type = new NotificationType( + "textDocument/formattingFailed", + ); +} + +// ------------------------------- ----------------------------------- + // ------------------------------- < IntelliSense Ready Event > ------------------------------------ /** diff --git a/extensions/mssql/src/sharedInterfaces/telemetry.ts b/extensions/mssql/src/sharedInterfaces/telemetry.ts index e0b1d73b69..cf7ae9c7c0 100644 --- a/extensions/mssql/src/sharedInterfaces/telemetry.ts +++ b/extensions/mssql/src/sharedInterfaces/telemetry.ts @@ -292,6 +292,8 @@ export enum TelemetryActions { Stats = "Stats", // ------------------------------- < SQL Tools Service > ------------------------------------ FormatCode = "FormatCode", + FormatterParseErrorSendFeedback = "FormatterParseErrorSendFeedback", + FormatterParseErrorDontShowAgain = "FormatterParseErrorDontShowAgain", PeekDefinitionRequested = "PeekDefinitionRequested", IntellisenseQuantile = "IntellisenseQuantile", // ------------------------------- ----------------------------------- diff --git a/extensions/mssql/test/unit/formatterConfiguration.test.ts b/extensions/mssql/test/unit/formatterConfiguration.test.ts index 7da67b6d42..b22d238555 100644 --- a/extensions/mssql/test/unit/formatterConfiguration.test.ts +++ b/extensions/mssql/test/unit/formatterConfiguration.test.ts @@ -10,6 +10,7 @@ import * as path from "path"; interface ConfigurationProperty { type: string; default: boolean | number | string; + markdownDescription?: string; enum?: string[]; minimum?: number; maximum?: number; @@ -67,6 +68,11 @@ function getConfigurationProperties(): Record { return packageManifest.contributes.configuration.properties; } +function getPackageNls(): Record { + const packageNlsPath = path.join(__dirname, "..", "..", "..", "package.nls.json"); + return JSON.parse(fs.readFileSync(packageNlsPath, "utf8")) as Record; +} + suite("SQL formatter configuration", () => { test("enables the new formatter by default", () => { const previewSetting = getConfigurationProperties()["mssql.format.enablePreviewFormatter"]; @@ -75,6 +81,13 @@ suite("SQL formatter configuration", () => { expect(previewSetting.scope).to.equal("window"); }); + test("enables parse-error notifications by default", () => { + const setting = getConfigurationProperties()["mssql.format.showParseErrorNotification"]; + + expect(setting.default).to.equal(true); + expect(setting.scope).to.equal("window"); + }); + test("contributes the currently supported ScriptDom settings", () => { const properties = getConfigurationProperties(); const prefix = "mssql.format.options."; @@ -119,4 +132,31 @@ suite("SQL formatter configuration", () => { expect(properties[prefix + "numNewlinesAfterStatement"].minimum).to.equal(0); expect(properties[prefix + "numNewlinesAfterStatement"].maximum).to.equal(5); }); + + test("identifies which formatter uses each option", () => { + const descriptions = getPackageNls(); + const properties = getConfigurationProperties(); + const previewPrefix = "**Used when Preview Formatter is enabled.**"; + const existingPrefix = "**Used when Preview Formatter is disabled.**"; + const previewSettings = Object.keys(expectedFormatterDefaults).map( + (key) => `mssql.format.options.${key}`, + ); + const existingSettings = [ + "mssql.format.alignColumnDefinitionsInColumns", + "mssql.format.datatypeCasing", + "mssql.format.keywordCasing", + "mssql.format.placeCommasBeforeNextStatement", + "mssql.format.placeSelectStatementReferencesOnNewLine", + ]; + + for (const setting of previewSettings) { + expect(descriptions[setting].startsWith(previewPrefix), setting).to.be.true; + expect(properties[setting].markdownDescription, setting).to.equal(`%${setting}%`); + } + for (const setting of existingSettings) { + expect(descriptions[setting].startsWith(existingPrefix), setting).to.be.true; + expect(properties[setting].markdownDescription, setting).to.equal(`%${setting}%`); + } + expect(descriptions["mssql.format.enablePreviewFormatter"]).not.to.include("legacy"); + }); }); diff --git a/extensions/mssql/test/unit/serviceClient.test.ts b/extensions/mssql/test/unit/serviceClient.test.ts index d5ab9253dc..b003ca1bd0 100644 --- a/extensions/mssql/test/unit/serviceClient.test.ts +++ b/extensions/mssql/test/unit/serviceClient.test.ts @@ -9,7 +9,11 @@ import * as chai from "chai"; import { expect } from "chai"; import * as vscode from "vscode"; import * as Constants from "../../src/constants/constants"; -import { ServiceClient as ServiceClientLoc } from "../../src/constants/locConstants"; +import { + Common as CommonLoc, + Formatter as FormatterLoc, + ServiceClient as ServiceClientLoc, +} from "../../src/constants/locConstants"; import ServerProvider from "../../src/languageservice/server"; import SqlToolsServiceClient from "../../src/languageservice/serviceclient"; import DotnetRuntimeProvider from "../../src/languageservice/dotnetRuntimeProvider"; @@ -18,7 +22,7 @@ import StatusView from "../../src/views/statusView"; import * as LanguageServiceContracts from "../../src/models/contracts/languageService"; import { Logger } from "../../src/models/logger"; import { TelemetryActions, TelemetryViews } from "../../src/sharedInterfaces/telemetry"; -import { stubTelemetry, stubVscodeEnv } from "./utils"; +import { stubMessageBoxes, stubTelemetry, stubVscodeEnv } from "./utils"; chai.use(sinonChai); @@ -41,6 +45,7 @@ suite("Service Client tests", () => { let testServiceProvider: sinon.SinonStubbedInstance; let testStatusView: sinon.SinonStubbedInstance; let loggerShowStub: sinon.SinonStub; + let loggerErrorStub: sinon.SinonStub; let dotnetRuntimeProvider: sinon.SinonStubbedInstance; let sendActionEvent: sinon.SinonStub; let originalStsOverride: string | undefined; @@ -50,6 +55,7 @@ suite("Service Client tests", () => { testServiceProvider = sandbox.createStubInstance(ServerProvider); testStatusView = sandbox.createStubInstance(StatusView); loggerShowStub = sandbox.stub(Logger.prototype, "show"); + loggerErrorStub = sandbox.stub(Logger.prototype, "error"); dotnetRuntimeProvider = sandbox.createStubInstance(DotnetRuntimeProvider); ({ sendActionEvent } = stubTelemetry(sandbox)); originalStsOverride = process.env.MSSQL_SQLTOOLSSERVICE; @@ -148,6 +154,107 @@ suite("Service Client tests", () => { ); }); + function formattingFailedEvent(): LanguageServiceContracts.FormattingFailedParams { + return { + ownerUri: "file:///query.sql", + formatType: "Document", + reason: "ParseError", + parseErrorCount: 1, + }; + } + + function stubFormatterNotificationConfiguration(enabled = true): sinon.SinonStub { + const update = sandbox.stub().resolves(); + sandbox.stub(vscode.workspace, "getConfiguration").returns({ + get: sandbox.stub().returns(enabled), + update, + } as unknown as vscode.WorkspaceConfiguration); + return update; + } + + test("opens formatter feedback from a parse-error notification", async () => { + const serviceClient = createServiceClient(); + stubFormatterNotificationConfiguration(); + const showWarningMessage = stubMessageBoxes(sandbox).showWarningMessage.resolves( + FormatterLoc.sendFeedback, + ); + const openExternal = stubVscodeEnv(sandbox).openExternal.resolves(true); + + await serviceClient.showFormattingFailedNotification(formattingFailedEvent()); + + expect(showWarningMessage).to.have.been.calledWith( + FormatterLoc.parseError, + FormatterLoc.sendFeedback, + CommonLoc.dontShowAgain, + ); + expect(openExternal).to.have.been.calledWith(vscode.Uri.parse(Constants.feedbackUrl)); + expect(sendActionEvent).to.have.been.calledWith( + TelemetryViews.QueryEditor, + TelemetryActions.FormatterParseErrorSendFeedback, + ); + }); + + test("persists formatter parse-error notification suppression", async () => { + const serviceClient = createServiceClient(); + const update = stubFormatterNotificationConfiguration(); + stubMessageBoxes(sandbox).showWarningMessage.resolves(CommonLoc.dontShowAgain); + + await serviceClient.showFormattingFailedNotification(formattingFailedEvent()); + + expect(update).to.have.been.calledWith( + "format.showParseErrorNotification", + false, + vscode.ConfigurationTarget.Global, + ); + expect(sendActionEvent).to.have.been.calledWith( + TelemetryViews.QueryEditor, + TelemetryActions.FormatterParseErrorDontShowAgain, + ); + }); + + test("logs formatter notification handling failures", async () => { + const serviceClient = createServiceClient(); + const error = new Error("notification failed"); + sandbox.stub(serviceClient, "showFormattingFailedNotification").rejects(error); + + serviceClient.handleFormattingFailedNotification()(formattingFailedEvent()); + await new Promise((resolve) => setImmediate(resolve)); + + expect(loggerErrorStub).to.have.been.calledWith( + "Failed to handle formatting failure notification.", + error.message, + ); + }); + + test("does not show a suppressed formatter parse-error notification", async () => { + const serviceClient = createServiceClient(); + stubFormatterNotificationConfiguration(false); + const showWarningMessage = stubMessageBoxes(sandbox).showWarningMessage; + + await serviceClient.showFormattingFailedNotification(formattingFailedEvent()); + + expect(showWarningMessage).not.to.have.been.called; + }); + + test("allows only one formatter parse-error prompt at a time", async () => { + const serviceClient = createServiceClient(); + stubFormatterNotificationConfiguration(); + let resolvePrompt: (action: string | undefined) => void; + const promptResult = new Promise((resolve) => { + resolvePrompt = resolve; + }); + const showWarningMessage = + stubMessageBoxes(sandbox).showWarningMessage.returns(promptResult); + + const firstPrompt = serviceClient.showFormattingFailedNotification(formattingFailedEvent()); + const secondPrompt = + serviceClient.showFormattingFailedNotification(formattingFailedEvent()); + + expect(showWarningMessage).to.have.been.calledOnce; + resolvePrompt(undefined); + await Promise.all([firstPrompt, secondPrompt]); + }); + function stubLaunches( serviceClient: SqlToolsServiceClient, launchImpl?: (attempt: ILaunchAttempt) => Promise, diff --git a/localization/xliff/vscode-mssql.xlf b/localization/xliff/vscode-mssql.xlf index 6c1186a71b..e3a95a8d97 100644 --- a/localization/xliff/vscode-mssql.xlf +++ b/localization/xliff/vscode-mssql.xlf @@ -6263,6 +6263,9 @@ SQL database in Fabric + + SQL formatting could not be completed because the T-SQL could not be fully parsed. If you believe the syntax is valid, please send feedback. + SQLAlchemy @@ -6764,6 +6767,9 @@ Selection + + Send Feedback + Sequelize @@ -8625,29 +8631,131 @@ - - Add Connection + + **Used when Preview Formatter is disabled.** Should column definitions be aligned? - - Add Connection Group + + **Used when Preview Formatter is disabled.** Should commas be placed at the beginning of each statement in a list, for example `, mycolumn2`, instead of at the end, for example `mycolumn1,`? - - Add Microsoft Entra Account + + **Used when Preview Formatter is disabled.** Should data types be formatted as UPPERCASE, lowercase, or none (not formatted) + + + **Used when Preview Formatter is disabled.** Should keywords be formatted as UPPERCASE, lowercase, or none (not formatted) + + + **Used when Preview Formatter is disabled.** Should references in a SELECT statement be split into separate lines? For `SELECT C1, C2 FROM T1`, both C1 and C2 will be on separate lines. - Align SET clause items in UPDATE statements. + **Used when Preview Formatter is enabled.** Align SET clause items in UPDATE statements. - Align column-definition fields, such as names, data types, and constraints. + **Used when Preview Formatter is enabled.** Align column-definition fields, such as names, data types, and constraints. - Align the bodies of FROM, WHERE, GROUP BY, and similar clauses. + **Used when Preview Formatter is enabled.** Align the bodies of FROM, WHERE, GROUP BY, and similar clauses. - Allow external language content to use file paths. + **Used when Preview Formatter is enabled.** Allow external language content to use file paths. - Allow external library content to use file paths. + **Used when Preview Formatter is enabled.** Allow external library content to use file paths. + + + **Used when Preview Formatter is enabled.** Format INSERT columns as multiline lists. + + + **Used when Preview Formatter is enabled.** Format INSERT sources as multiline lists. + + + **Used when Preview Formatter is enabled.** Format SELECT columns as multiline lists. + + + **Used when Preview Formatter is enabled.** Format SET clause items as multiline lists. + + + **Used when Preview Formatter is enabled.** Format VIEW columns as multiline lists. + + + **Used when Preview Formatter is enabled.** Format WHERE predicates as multiline lists. + + + **Used when Preview Formatter is enabled.** Indent SET clauses in UPDATE statements. + + + **Used when Preview Formatter is enabled.** Indent VIEW bodies. + + + **Used when Preview Formatter is enabled.** Insert a space between a data type and its parentheses, for example `VARCHAR (255)`. + + + **Used when Preview Formatter is enabled.** Insert spaces between parameters in a data type, for example `DECIMAL (10, 2)`. + + + **Used when Preview Formatter is enabled.** Keyword casing style. + + + **Used when Preview Formatter is enabled.** Number of line breaks after each statement, from 0 through 5. + + + **Used when Preview Formatter is enabled.** Place AS on its own line. + + + **Used when Preview Formatter is enabled.** Place UNIQUE, INCLUDE, and WHERE portions of inline index definitions on separate lines. + + + **Used when Preview Formatter is enabled.** Place a newline before JOIN clauses. + + + **Used when Preview Formatter is enabled.** Place a newline before the FROM clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the GROUP BY clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the HAVING clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the OFFSET clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the ORDER BY clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the OUTPUT clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the WHERE clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the WINDOW clause. + + + **Used when Preview Formatter is enabled.** Place a newline before the closing parenthesis of a multiline list. + + + **Used when Preview Formatter is enabled.** Place a newline before the opening parenthesis of a multiline list. + + + **Used when Preview Formatter is enabled.** Place the CHECK clause of a constraint on its own line. + + + **Used when Preview Formatter is enabled.** Preserve comments during formatting. + + + **Used when Preview Formatter is enabled.** SQL engine type used to parse and generate formatted scripts. + + + **Used when Preview Formatter is enabled.** T-SQL version used to parse and generate formatted scripts. + + + Add Connection + + + Add Connection Group + + + Add Microsoft Entra Account Allows users to sign in to input-constrained devices. @@ -8976,24 +9084,6 @@ Filter your Object Explorer Tree - - Format INSERT columns as multiline lists. - - - Format INSERT sources as multiline lists. - - - Format SELECT columns as multiline lists. - - - Format SET clause items as multiline lists. - - - Format VIEW columns as multiline lists. - - - Format WHERE predicates as multiline lists. - Generate Script @@ -9012,39 +9102,9 @@ Import Data... - - Indent SET clauses in UPDATE statements. - - - Indent VIEW bodies. - - - Insert a space between a data type and its parentheses, for example VARCHAR (255). - - - Insert spaces between parameters in a data type, for example DECIMAL (10, 2). - - - Keyword casing style. - Launch Query Profiler... - - Legacy formatter only: Align column definitions. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting. - - - Legacy formatter only: Control data type casing. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting. - - - Legacy formatter only: Control keyword and IntelliSense casing. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting. - - - Legacy formatter only: Place SELECT statement object references on separate lines. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting. - - - Legacy formatter only: Place commas at the beginning instead of the end of list items. Set `mssql.format.enablePreviewFormatter` to `false` to use this setting. - MSSQL @@ -9102,9 +9162,6 @@ Next Steps with MSSQL for Visual Studio Code - - Number of line breaks after each statement, from 0 through 5. - Number of minutes to retain log files for backend services. Default is 1 week. @@ -9153,51 +9210,6 @@ Pause Query History Capture - - Place AS on its own line. - - - Place UNIQUE, INCLUDE, and WHERE portions of inline index definitions on separate lines. - - - Place a newline before JOIN clauses. - - - Place a newline before the FROM clause. - - - Place a newline before the GROUP BY clause. - - - Place a newline before the HAVING clause. - - - Place a newline before the OFFSET clause. - - - Place a newline before the ORDER BY clause. - - - Place a newline before the OUTPUT clause. - - - Place a newline before the WHERE clause. - - - Place a newline before the WINDOW clause. - - - Place a newline before the closing parenthesis of a multiline list. - - - Place a newline before the opening parenthesis of a multiline list. - - - Place the CHECK clause of a constraint on its own line. - - - Preserve comments during formatting. - Prevent automatic execution of scripts (e.g., 'Select Top 1000'). When enabled, scripts will not be automatically executed upon generation. @@ -9291,9 +9303,6 @@ SQL Tools (MSSQL) - - SQL engine type used to parse and generate formatted scripts. - SQL text for this Quick Query. Use {arg} to insert selected query-editor text; otherwise, it is appended. @@ -9360,6 +9369,9 @@ Should query result selections and scroll positions be saved when switching tabs (may impact performance) + + Show a notification when Preview Formatter cannot fully parse the T-SQL being formatted. + Show both horizontal and vertical grid lines @@ -9420,9 +9432,6 @@ Stop SQL Container - - T-SQL version used to parse and generate formatted scripts. - Table Designer @@ -9499,7 +9508,7 @@ Use only row data to size columns automatically - Use the new SQL formatter. Disable this setting to temporarily use the legacy formatter. + Use the preview SQL formatter. Disable this setting to temporarily use the existing formatter. Uses VS Code signed-in accounts for Microsoft Entra ID MFA authentication to SQL. Restart Visual Studio Code after changing this setting.