From 8a6dc02e5b9e6856a79c7bf15866a33f5a274cfe Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Mon, 5 Aug 2019 09:46:22 -0700 Subject: [PATCH] Change double-quote hygiene rule to tslint rule (#6514) --- build/gulpfile.hygiene.js | 40 +++++---- build/lib/tslint/doubleQuotedStringArgRule.js | 60 ++++++++++++++ build/lib/tslint/doubleQuotedStringArgRule.ts | 82 +++++++++++++++++++ .../query/common/queryModelService.ts | 4 +- src/sql/platform/query/common/queryRunner.ts | 8 +- .../api/common/extHostModelViewTree.ts | 2 +- .../workbench/api/common/extHostNotebook.ts | 6 +- .../electron-browser/scriptingUtils.ts | 4 +- .../workbench/parts/charts/browser/actions.ts | 2 +- .../parts/charts/browser/graphInsight.ts | 4 +- .../electron-browser/commandLine.ts | 6 +- .../dashboardErrorContainer.component.ts | 2 +- .../dashboard/browser/core/dashboardHelper.ts | 2 +- .../browser/outputs/plotlyOutput.component.ts | 2 +- .../profiler/browser/profilerTableEditor.ts | 4 +- .../parts/query/browser/query.contribution.ts | 2 +- .../parts/tasks/browser/tasksView.ts | 2 +- .../connection/browser/connectionWidget.ts | 6 +- .../browser/fileBrowserTreeView.ts | 2 +- .../notebook/common/localContentManager.ts | 12 +-- .../queryEditor/browser/queryEditorService.ts | 4 +- tslint-gci.json | 5 +- tslint-sql.json | 20 +++++ 23 files changed, 228 insertions(+), 53 deletions(-) create mode 100644 build/lib/tslint/doubleQuotedStringArgRule.js create mode 100644 build/lib/tslint/doubleQuotedStringArgRule.ts create mode 100644 tslint-sql.json diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 4c2f1cb5dd..f0b3295720 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -289,19 +289,6 @@ function hygiene(some) { this.emit('data', file); }); - - const localizeDoubleQuotes = es.through(function (file) { - const lines = file.__lines; - lines.forEach((line, i) => { - if (/localize\(['"].*['"],\s'.*'\)/.test(line)) { - console.error(file.relative + '(' + (i + 1) + ',1): Message parameter to localize calls should be double-quotes'); - errorCount++; - } - }); - - this.emit('data', file); - }); - // {{SQL CARBON EDIT}} END const formatting = es.map(function (file, cb) { @@ -352,6 +339,17 @@ function hygiene(some) { input = some; } + const tslintSqlConfiguration = tslint.Configuration.findConfiguration('tslint-sql.json', '.'); + const tslintSqlOptions = { fix: false, formatter: 'json' }; + const sqlTsLinter = new tslint.Linter(tslintSqlOptions); + + const sqlTsl = es.through(function (file) { + const contents = file.contents.toString('utf8'); + sqlTsLinter.lint(file.relative, contents, tslintSqlConfiguration.results); + + this.emit('data', file); + }); + const productJsonFilter = filter('product.json', { restore: true }); const result = input @@ -371,9 +369,8 @@ function hygiene(some) { // {{SQL CARBON EDIT}} .pipe(filter(useStrictFilter)) .pipe(useStrict) - // Only look at files under the sql folder since we don't want to cause conflicts with VS code .pipe(filter(sqlFilter)) - .pipe(localizeDoubleQuotes); + .pipe(sqlTsl); const javascript = result .pipe(filter(eslintFilter)) @@ -405,6 +402,19 @@ function hygiene(some) { errorCount += tslintResult.failures.length; } + const sqlTslintResult = sqlTsLinter.getResult(); + if (sqlTslintResult.failures.length > 0) { + for (const failure of sqlTslintResult.failures) { + const name = failure.getFileName(); + const position = failure.getStartPosition(); + const line = position.getLineAndCharacter().line; + const character = position.getLineAndCharacter().character; + + console.error(`${name}:${line + 1}:${character + 1}:${failure.getFailure()}`); + } + errorCount += sqlTslintResult.failures.length; + } + if (errorCount > 0) { this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.'); } else { diff --git a/build/lib/tslint/doubleQuotedStringArgRule.js b/build/lib/tslint/doubleQuotedStringArgRule.js new file mode 100644 index 0000000000..3787836482 --- /dev/null +++ b/build/lib/tslint/doubleQuotedStringArgRule.js @@ -0,0 +1,60 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const ts = require("typescript"); +const Lint = require("tslint"); +/** + * Implementation of the double-quoted-string-arg rule which verifies that the specified index of calls matching + * the specified signatures is quoted with double-quotes only. + */ +class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile) { + return this.applyWithWalker(new DoubleQuotedStringArgRuleWalker(sourceFile, this.getOptions())); + } +} +exports.Rule = Rule; +class DoubleQuotedStringArgRuleWalker extends Lint.RuleWalker { + constructor(file, opts) { + super(file, opts); + this.signatures = Object.create(null); + this.argIndex = undefined; + const options = this.getOptions(); + const first = options && options.length > 0 ? options[0] : null; + if (first) { + if (Array.isArray(first.signatures)) { + first.signatures.forEach((signature) => this.signatures[signature] = true); + } + if (typeof first.argIndex !== 'undefined') { + this.argIndex = first.argIndex; + } + } + } + visitCallExpression(node) { + this.checkCallExpression(node); + super.visitCallExpression(node); + } + checkCallExpression(node) { + // Not one of the functions we're looking for, continue on + const functionName = node.expression.getText(); + if (functionName && !this.signatures[functionName]) { + return; + } + const arg = node.arguments[this.argIndex]; + // Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue + if (arg && ts.isStringLiteral(arg)) { + const argText = arg.getText(); + const doubleQuotedArg = argText.length >= 2 && argText[0] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE && argText[argText.length - 1] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE; + if (!doubleQuotedArg) { + const fix = [ + Lint.Replacement.replaceFromTo(arg.getStart(), arg.getWidth(), `"${arg.getText().slice(1, arg.getWidth() - 2)}"`), + ]; + this.addFailure(this.createFailure(arg.getStart(), arg.getWidth(), `Argument ${this.argIndex + 1} to '${functionName}' must be double quoted.`, fix)); + return; + } + } + } +} +DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE = '"'; diff --git a/build/lib/tslint/doubleQuotedStringArgRule.ts b/build/lib/tslint/doubleQuotedStringArgRule.ts new file mode 100644 index 0000000000..06d6a347aa --- /dev/null +++ b/build/lib/tslint/doubleQuotedStringArgRule.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as Lint from 'tslint'; + +/** + * Implementation of the double-quoted-string-arg rule which verifies that the specified index of calls matching + * the specified signatures is quoted with double-quotes only. + */ +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new DoubleQuotedStringArgRuleWalker(sourceFile, this.getOptions())); + } +} + +interface Map { + [key: string]: V; +} + +interface DoubleQuotedStringArgOptions { + signatures?: string[]; + argIndex?: number; +} + +class DoubleQuotedStringArgRuleWalker extends Lint.RuleWalker { + + private static DOUBLE_QUOTE: string = '"'; + + private signatures: Map; + private argIndex: number | undefined; + + constructor(file: ts.SourceFile, opts: Lint.IOptions) { + super(file, opts); + this.signatures = Object.create(null); + this.argIndex = undefined; + const options: any[] = this.getOptions(); + const first: DoubleQuotedStringArgOptions = options && options.length > 0 ? options[0] : null; + if (first) { + if (Array.isArray(first.signatures)) { + first.signatures.forEach((signature: string) => this.signatures[signature] = true); + } + if (typeof first.argIndex !== 'undefined') { + this.argIndex = first.argIndex; + } + } + } + + protected visitCallExpression(node: ts.CallExpression): void { + this.checkCallExpression(node); + super.visitCallExpression(node); + } + + private checkCallExpression(node: ts.CallExpression): void { + // Not one of the functions we're looking for, continue on + const functionName = node.expression.getText(); + if (functionName && !this.signatures[functionName]) { + return; + } + + const arg = node.arguments[this.argIndex!]; + + // Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue + if(arg && ts.isStringLiteral(arg)) { + const argText = arg.getText(); + const doubleQuotedArg = argText.length >= 2 && argText[0] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE && argText[argText.length - 1] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE; + + if (!doubleQuotedArg) { + const fix = [ + Lint.Replacement.replaceFromTo(arg.getStart(), arg.getWidth(), `"${arg.getText().slice(1, arg.getWidth() - 2)}"`), + ]; + this.addFailure(this.createFailure( + arg.getStart(), arg.getWidth(), + `Argument ${this.argIndex! + 1} to '${functionName}' must be double quoted.`, fix)); + return; + } + } + + } +} diff --git a/src/sql/platform/query/common/queryModelService.ts b/src/sql/platform/query/common/queryModelService.ts index 30653fe750..7f58af9877 100644 --- a/src/sql/platform/query/common/queryModelService.ts +++ b/src/sql/platform/query/common/queryModelService.ts @@ -254,7 +254,7 @@ export class QueryModelService implements IQueryModelService { if (info.selectionSnippet) { // This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the // executed query text - messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet); + messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); } else { link = { text: strings.format(LocalizedConstants.runQueryBatchStartLine, b.selection.startLine + 1) @@ -405,7 +405,7 @@ export class QueryModelService implements IQueryModelService { if (info.selectionSnippet) { // This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the // executed query text - messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet); + messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet); } else { link = { text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1) diff --git a/src/sql/platform/query/common/queryRunner.ts b/src/sql/platform/query/common/queryRunner.ts index 77bbcc5e88..83feefe7b5 100644 --- a/src/sql/platform/query/common/queryRunner.ts +++ b/src/sql/platform/query/common/queryRunner.ts @@ -218,7 +218,7 @@ export default class QueryRunner extends Disposable { if (error instanceof Error) { error = error.message; } - let message = nls.localize('query.ExecutionFailedError', 'Execution failed due to an unexpected error: {0}\t{1}', eol, error); + let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error); this.handleMessage({ ownerUri: this.uri, message: { @@ -252,7 +252,7 @@ export default class QueryRunner extends Disposable { // We're done with this query so shut down any waiting mechanisms let message = { - message: nls.localize('query.message.executionTime', 'Total execution time: {0}', timeStamp), + message: nls.localize('query.message.executionTime', "Total execution time: {0}", timeStamp), isError: false, time: undefined }; @@ -282,7 +282,7 @@ export default class QueryRunner extends Disposable { let message = { // account for index by 1 - message: nls.localize('query.message.startQuery', 'Started executing query at Line {0}', batch.selection.startLine + 1), + message: nls.localize('query.message.startQuery', "Started executing query at Line {0}", batch.selection.startLine + 1), time: new Date(batch.executionStart).toLocaleTimeString(), selection: batch.selection, isError: false @@ -563,7 +563,7 @@ export default class QueryRunner extends Disposable { if (showBatchTime) { let message: IQueryMessage = { batchId: batchId, - message: nls.localize('elapsedBatchTime', 'Batch execution time: {0}', executionTime), + message: nls.localize('elapsedBatchTime', "Batch execution time: {0}", executionTime), time: undefined, isError: false }; diff --git a/src/sql/workbench/api/common/extHostModelViewTree.ts b/src/sql/workbench/api/common/extHostModelViewTree.ts index a59ff2e649..0e3c911919 100644 --- a/src/sql/workbench/api/common/extHostModelViewTree.ts +++ b/src/sql/workbench/api/common/extHostModelViewTree.ts @@ -45,7 +45,7 @@ export class ExtHostModelViewTreeViews implements ExtHostModelViewTreeViewsShape const treeView = this.treeViews.get(treeViewId); if (!treeView) { - return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId))); + return Promise.reject(new Error(localize('treeView.notRegistered', "No tree view with id \'{0}\' registered.", treeViewId))); } return treeView.getChildren(treeItemHandle); } diff --git a/src/sql/workbench/api/common/extHostNotebook.ts b/src/sql/workbench/api/common/extHostNotebook.ts index 4e50f1eec0..9579f16e1b 100644 --- a/src/sql/workbench/api/common/extHostNotebook.ts +++ b/src/sql/workbench/api/common/extHostNotebook.ts @@ -289,7 +289,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape { return this._withNotebookManager(handle, (notebookManager) => { let serverManager = notebookManager.serverManager; if (!serverManager) { - return Promise.reject(new Error(localize('noServerManager', 'Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it', notebookManager.uriString))); + return Promise.reject(new Error(localize('noServerManager', "Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it", notebookManager.uriString))); } return callback(serverManager); }); @@ -299,7 +299,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape { return this._withNotebookManager(handle, (notebookManager) => { let contentManager = notebookManager.contentManager; if (!contentManager) { - return Promise.reject(new Error(localize('noContentManager', 'Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it', notebookManager.uriString))); + return Promise.reject(new Error(localize('noContentManager', "Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it", notebookManager.uriString))); } return callback(contentManager); }); @@ -309,7 +309,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape { return this._withNotebookManager(handle, (notebookManager) => { let sessionManager = notebookManager.sessionManager; if (!sessionManager) { - return Promise.reject(new Error(localize('noSessionManager', 'Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it', notebookManager.uriString))); + return Promise.reject(new Error(localize('noSessionManager', "Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it", notebookManager.uriString))); } return callback(sessionManager); }); diff --git a/src/sql/workbench/electron-browser/scriptingUtils.ts b/src/sql/workbench/electron-browser/scriptingUtils.ts index e7281e450e..e31aeb5baf 100644 --- a/src/sql/workbench/electron-browser/scriptingUtils.ts +++ b/src/sql/workbench/electron-browser/scriptingUtils.ts @@ -164,7 +164,7 @@ export function script(connectionProfile: IConnectionProfile, metadata: azdata.O reject(editorError); }); } else { - let scriptNotFoundMsg = nls.localize('scriptNotFoundForObject', 'No script was returned when scripting as {0} on object {1}', + let scriptNotFoundMsg = nls.localize('scriptNotFoundForObject', "No script was returned when scripting as {0} on object {1}", GetScriptOperationName(operation), metadata.metadataTypeName); let messageDetail = ''; let operationResult = scriptingService.getOperationFailedResult(result.operationId); @@ -179,7 +179,7 @@ export function script(connectionProfile: IConnectionProfile, metadata: azdata.O reject(scriptNotFoundMsg); } } else { - reject(nls.localize('scriptNotFound', 'No script was returned when scripting as {0}', GetScriptOperationName(operation))); + reject(nls.localize('scriptNotFound', "No script was returned when scripting as {0}", GetScriptOperationName(operation))); } }, scriptingError => { reject(scriptingError); diff --git a/src/sql/workbench/parts/charts/browser/actions.ts b/src/sql/workbench/parts/charts/browser/actions.ts index c42cba6c6d..ebf7c0cc17 100644 --- a/src/sql/workbench/parts/charts/browser/actions.ts +++ b/src/sql/workbench/parts/charts/browser/actions.ts @@ -169,7 +169,7 @@ export class SaveImageAction extends Action { this.windowsService.openExternal(filePath.toString()); this.notificationService.notify({ severity: Severity.Error, - message: localize('chartSaved', 'Saved Chart to path: {0}', filePath.toString()) + message: localize('chartSaved', "Saved Chart to path: {0}", filePath.toString()) }); } } diff --git a/src/sql/workbench/parts/charts/browser/graphInsight.ts b/src/sql/workbench/parts/charts/browser/graphInsight.ts index 0207bba41d..dd591a0fdc 100644 --- a/src/sql/workbench/parts/charts/browser/graphInsight.ts +++ b/src/sql/workbench/parts/charts/browser/graphInsight.ts @@ -128,7 +128,7 @@ export class Graph implements IInsight { chartData = data.rows.map((row, i) => { return { data: row.map(item => Number(item)), - label: localize('series', 'Series {0}', i) + label: localize('series', "Series {0}", i) }; }); } @@ -144,7 +144,7 @@ export class Graph implements IInsight { chartData = data.rows[0].slice(1).map((row, i) => { return { data: data.rows.map(row => Number(row[i + 1])), - label: localize('series', 'Series {0}', i + 1) + label: localize('series', "Series {0}", i + 1) }; }); } diff --git a/src/sql/workbench/parts/commandLine/electron-browser/commandLine.ts b/src/sql/workbench/parts/commandLine/electron-browser/commandLine.ts index 0465a7c219..b6efc305aa 100644 --- a/src/sql/workbench/parts/commandLine/electron-browser/commandLine.ts +++ b/src/sql/workbench/parts/commandLine/electron-browser/commandLine.ts @@ -92,7 +92,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution let connectedContext: azdata.ConnectedContext = undefined; if (profile) { if (this._notificationService) { - this._notificationService.status(localize('connectingLabel', 'Connecting: {0}', profile.serverName), { hideAfter: 2500 }); + this._notificationService.status(localize('connectingLabel', "Connecting: {0}", profile.serverName), { hideAfter: 2500 }); } try { await this._connectionManagementService.connectIfNotConnected(profile, 'connection', true); @@ -106,7 +106,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution } if (commandName) { if (this._notificationService) { - this._notificationService.status(localize('runningCommandLabel', 'Running command: {0}', commandName), { hideAfter: 2500 }); + this._notificationService.status(localize('runningCommandLabel', "Running command: {0}", commandName), { hideAfter: 2500 }); } await this._commandService.executeCommand(commandName, connectedContext); } else if (profile) { @@ -119,7 +119,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution else { // Default to showing new query if (this._notificationService) { - this._notificationService.status(localize('openingNewQueryLabel', 'Opening new query: {0}', profile.serverName), { hideAfter: 2500 }); + this._notificationService.status(localize('openingNewQueryLabel', "Opening new query: {0}", profile.serverName), { hideAfter: 2500 }); } try { await TaskUtilities.newQuery(profile, diff --git a/src/sql/workbench/parts/dashboard/browser/containers/dashboardErrorContainer.component.ts b/src/sql/workbench/parts/dashboard/browser/containers/dashboardErrorContainer.component.ts index b99ccf7359..20bcf4e003 100644 --- a/src/sql/workbench/parts/dashboard/browser/containers/dashboardErrorContainer.component.ts +++ b/src/sql/workbench/parts/dashboard/browser/containers/dashboardErrorContainer.component.ts @@ -40,7 +40,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn ngAfterViewInit() { const errorMessage = this._errorMessageContainer.nativeElement as HTMLElement; - errorMessage.innerText = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title); + errorMessage.innerText = nls.localize('dashboardNavSection_loadTabError', "The \"{0}\" section has invalid content. Please contact extension owner.", this.tab.title); } public get id(): string { diff --git a/src/sql/workbench/parts/dashboard/browser/core/dashboardHelper.ts b/src/sql/workbench/parts/dashboard/browser/core/dashboardHelper.ts index 9c58dd6f8d..98a5f3532c 100644 --- a/src/sql/workbench/parts/dashboard/browser/core/dashboardHelper.ts +++ b/src/sql/workbench/parts/dashboard/browser/core/dashboardHelper.ts @@ -179,7 +179,7 @@ export function getDashboardContainer(container: object, logService: ILogService if (!containerTypeFound) { const dashboardContainer = dashboardcontainerRegistry.getRegisteredContainer(key); if (!dashboardContainer) { - const errorMessage = nls.localize('unknownDashboardContainerError', '{0} is an unknown container.', key); + const errorMessage = nls.localize('unknownDashboardContainerError', "{0} is an unknown container.", key); logService.error(errorMessage); return { result: false, message: errorMessage, container: undefined }; } else { diff --git a/src/sql/workbench/parts/notebook/browser/outputs/plotlyOutput.component.ts b/src/sql/workbench/parts/notebook/browser/outputs/plotlyOutput.component.ts index a6ca256627..7f5e8dd971 100644 --- a/src/sql/workbench/parts/notebook/browser/outputs/plotlyOutput.component.ts +++ b/src/sql/workbench/parts/notebook/browser/outputs/plotlyOutput.component.ts @@ -143,7 +143,7 @@ export class PlotlyOutputComponent extends AngularDisposable implements IMimeCom } private displayError(error: Error | string): void { - this.errorText = localize('plotlyError', 'Error displaying Plotly graph: {0}', getErrorMessage(error)); + this.errorText = localize('plotlyError', "Error displaying Plotly graph: {0}", getErrorMessage(error)); } layout(): void { diff --git a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts index b3cd864d5d..5ea46eceba 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts @@ -284,8 +284,8 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll private _updateRowCountStatus(): void { if (this._showStatusBarItem) { let message = this._input.data.filterEnabled ? - localize('ProfilerTableEditor.eventCountFiltered', 'Events (Filtered): {0}/{1}', this._input.data.getLength(), this._input.data.getLengthNonFiltered()) - : localize('ProfilerTableEditor.eventCount', 'Events: {0}', this._input.data.getLength()); + localize('ProfilerTableEditor.eventCountFiltered', "Events (Filtered): {0}/{1}", this._input.data.getLength(), this._input.data.getLengthNonFiltered()) + : localize('ProfilerTableEditor.eventCount', "Events: {0}", this._input.data.getLength()); this._disposeStatusbarItem(); this._statusbarItem = this._statusbarService.addEntry({ text: message }, 'status.eventCount', localize('status.eventCount', "Event Count"), StatusbarAlignment.RIGHT); diff --git a/src/sql/workbench/parts/query/browser/query.contribution.ts b/src/sql/workbench/parts/query/browser/query.contribution.ts index 56c09509eb..a51e154be8 100644 --- a/src/sql/workbench/parts/query/browser/query.contribution.ts +++ b/src/sql/workbench/parts/query/browser/query.contribution.ts @@ -506,7 +506,7 @@ for (let i = 0; i < 9; i++) { 'type': 'string', 'default': defaultVal, 'description': localize('queryShortcutDescription', - 'Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter', + "Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter", queryIndex) }; } diff --git a/src/sql/workbench/parts/tasks/browser/tasksView.ts b/src/sql/workbench/parts/tasks/browser/tasksView.ts index 02e0494f56..fd742d5ae3 100644 --- a/src/sql/workbench/parts/tasks/browser/tasksView.ts +++ b/src/sql/workbench/parts/tasks/browser/tasksView.ts @@ -92,7 +92,7 @@ export class TaskHistoryView { }, { indentPixels: 10, twistiePixels: 20, - ariaLabel: localize({ key: 'taskHistory.regTreeAriaLabel', comment: ['TaskHistory'] }, 'Task history') + ariaLabel: localize({ key: 'taskHistory.regTreeAriaLabel', comment: ['TaskHistory'] }, "Task history") }); } diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index 05b0cb8d84..03dbc3fc36 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -193,9 +193,9 @@ export class ConnectionWidget { validationOptions: { validation: (value: string) => { if (!value) { - return ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', '{0} is required.', serverNameOption.displayName) }); + return ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", serverNameOption.displayName) }); } else if (startsWith(value, ' ') || endsWith(value, ' ')) { - return ({ type: MessageType.WARNING, content: localize('connectionWidget.fieldWillBeTrimmed', '{0} will be trimmed.', serverNameOption.displayName) }); + return ({ type: MessageType.WARNING, content: localize('connectionWidget.fieldWillBeTrimmed', "{0} will be trimmed.", serverNameOption.displayName) }); } return undefined; } @@ -211,7 +211,7 @@ export class ConnectionWidget { let userName = DialogHelper.appendRow(this._tableContainer, userNameOption.displayName, 'connection-label', 'connection-input', 'username-password-row'); this._userNameInputBox = new InputBox(userName, this._contextViewService, { validationOptions: { - validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', '{0} is required.', userNameOption.displayName) }) : null + validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", userNameOption.displayName) }) : null }, ariaLabel: userNameOption.displayName }); diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts index f811575e1f..023b135f8d 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts @@ -78,7 +78,7 @@ export class FileBrowserTreeView implements IDisposable { }, { indentPixels: 10, twistiePixels: 12, - ariaLabel: nls.localize({ key: 'fileBrowser.regTreeAriaLabel', comment: ['FileBrowserTree'] }, 'File browser tree') + ariaLabel: nls.localize({ key: 'fileBrowser.regTreeAriaLabel', comment: ['FileBrowserTree'] }, "File browser tree") }); } diff --git a/src/sql/workbench/services/notebook/common/localContentManager.ts b/src/sql/workbench/services/notebook/common/localContentManager.ts index 64f76f37eb..7c5f7a92bb 100644 --- a/src/sql/workbench/services/notebook/common/localContentManager.ts +++ b/src/sql/workbench/services/notebook/common/localContentManager.ts @@ -38,7 +38,7 @@ export class LocalContentManager implements nb.ContentManager { return v3.readNotebook(contents); } if (contents.nbformat) { - throw new TypeError(localize('nbformatNotRecognized', 'nbformat v{0}.{1} not recognized', contents.nbformat as any, contents.nbformat_minor as any)); + throw new TypeError(localize('nbformatNotRecognized', "nbformat v{0}.{1} not recognized", contents.nbformat as any, contents.nbformat_minor as any)); } } @@ -69,7 +69,7 @@ export class LocalContentManager implements nb.ContentManager { return v3.readNotebook(contents); } if (contents.nbformat) { - throw new TypeError(localize('nbformatNotRecognized', 'nbformat v{0}.{1} not recognized', contents.nbformat as any, contents.nbformat_minor as any)); + throw new TypeError(localize('nbformatNotRecognized', "nbformat v{0}.{1} not recognized", contents.nbformat as any, contents.nbformat_minor as any)); } } @@ -131,7 +131,7 @@ namespace v4 { case 'code': return createCodeCell(cell); default: - throw new TypeError(localize('unknownCellType', 'Cell type {0} unknown', cell.cell_type)); + throw new TypeError(localize('unknownCellType', "Cell type {0} unknown", cell.cell_type)); } } @@ -190,7 +190,7 @@ namespace v4 { }; default: // Should never get here - throw new TypeError(localize('unrecognizedOutput', 'Output type {0} not recognized', (output).output_type)); + throw new TypeError(localize('unrecognizedOutput', "Output type {0} not recognized", (output).output_type)); } } @@ -222,7 +222,7 @@ namespace v4 { return demultiline(data); } - throw new TypeError(localize('invalidMimeData', 'Data for {0} is expected to be a string or an Array of strings', key)); + throw new TypeError(localize('invalidMimeData', "Data for {0} is expected to be a string or an Array of strings", key)); } export function demultiline(value: nb.MultilineString): string { @@ -314,7 +314,7 @@ namespace v3 { traceback: output.traceback }; default: - throw new TypeError(localize('unrecognizedOutputType', 'Output type {0} not recognized', output.output_type)); + throw new TypeError(localize('unrecognizedOutputType', "Output type {0} not recognized", output.output_type)); } }; diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index dcbfa92f5c..27c5905350 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -41,12 +41,12 @@ export class QueryEditorService implements IQueryEditorService { private static CHANGE_UNSUPPORTED_ERROR_MESSAGE = nls.localize( 'queryEditorServiceChangeUnsupportedError', - 'Change Language Mode is not supported for unsaved queries' + "Change Language Mode is not supported for unsaved queries" ); private static CHANGE_ERROR_MESSAGE = nls.localize( 'queryEditorServiceChangeError', - 'Please save or discard changes before switching to/from the SQL Language Mode' + "Please save or discard changes before switching to/from the SQL Language Mode" ); // service references for static functions diff --git a/tslint-gci.json b/tslint-gci.json index 760c977232..981db69e57 100644 --- a/tslint-gci.json +++ b/tslint-gci.json @@ -1,5 +1,8 @@ { - "extends": "./tslint.json", + "extends": [ + "./tslint.json", + "./tslint-sql.json" + ], "rules": { "no-banned-terms": true, "no-delete-expression": true, diff --git a/tslint-sql.json b/tslint-sql.json new file mode 100644 index 0000000000..9333318f92 --- /dev/null +++ b/tslint-sql.json @@ -0,0 +1,20 @@ +{ + "rulesDirectory": [ + "build/lib/tslint" + ], + "rules": { + "double-quoted-string-arg": [ + true, + { + "signatures": [ + "localize", + "nls.localize" + ], + "argIndex": 1 + } + ] + }, + "linterOptions": { + "exclude": ["src/vs/**/*.ts"] + } +} \ No newline at end of file