Change double-quote hygiene rule to tslint rule (#6514)

This commit is contained in:
Charles Gagnon
2019-08-05 09:46:22 -07:00
committed by GitHub
parent a5a37c97a9
commit 8a6dc02e5b
23 changed files with 228 additions and 53 deletions

View File

@@ -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 {

View File

@@ -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 = '"';

View File

@@ -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<V> {
[key: string]: V;
}
interface DoubleQuotedStringArgOptions {
signatures?: string[];
argIndex?: number;
}
class DoubleQuotedStringArgRuleWalker extends Lint.RuleWalker {
private static DOUBLE_QUOTE: string = '"';
private signatures: Map<boolean>;
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;
}
}
}
}

View File

@@ -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)

View File

@@ -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(<azdata.QueryExecuteMessageParams>{
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
};

View File

@@ -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);
}

View File

@@ -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);
});

View File

@@ -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);

View File

@@ -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())
});
}
}

View File

@@ -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)
};
});
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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)
};
}

View File

@@ -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")
});
}

View File

@@ -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
});

View File

@@ -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")
});
}

View File

@@ -38,7 +38,7 @@ export class LocalContentManager implements nb.ContentManager {
return v3.readNotebook(<any>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(<any>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', (<any>output).output_type));
throw new TypeError(localize('unrecognizedOutput', "Output type {0} not recognized", (<any>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));
}
};

View File

@@ -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

View File

@@ -1,5 +1,8 @@
{
"extends": "./tslint.json",
"extends": [
"./tslint.json",
"./tslint-sql.json"
],
"rules": {
"no-banned-terms": true,
"no-delete-expression": true,

20
tslint-sql.json Normal file
View File

@@ -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"]
}
}