From 570a5d4dc7928f75619b9f63e0cec8ba509dcd11 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 00:06:52 +0300 Subject: [PATCH 01/27] feat: add strict-string-expressions --- src/rules/strictStringExpressionsRule.ts | 86 ++++++++++++++ .../strict-string-expressions/test.ts.lint | 105 ++++++++++++++++++ .../strict-string-expressions/tsconfig.json | 5 + .../strict-string-expressions/tslint.json | 5 + 4 files changed, 201 insertions(+) create mode 100644 src/rules/strictStringExpressionsRule.ts create mode 100644 test/rules/strict-string-expressions/test.ts.lint create mode 100644 test/rules/strict-string-expressions/tsconfig.json create mode 100644 test/rules/strict-string-expressions/tslint.json diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts new file mode 100644 index 00000000000..28142e6997e --- /dev/null +++ b/src/rules/strictStringExpressionsRule.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright 2018 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from "typescript"; + +import * as Lint from "../index"; +import { isTypeFlagSet } from 'tsutils'; + +export class Rule extends Lint.Rules.TypedRule { + public static metadata: Lint.IRuleMetadata = { + ruleName: "strict-string-expressions", + description: Lint.Utils.dedent` + Require explicit toString() call for variables used in strings. By default only strings are allowed. + + The following nodes are checked: + + * String literals ("foo" + bar) + * ES6 templates (\`foo \${bar}\`)`, + type: "functionality", + typescriptOnly: true, + requiresTypeInfo: true, + options: [], + optionsDescription: '', + }; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); + } +} + +function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { + const { sourceFile } = ctx; + ts.forEachChild(sourceFile, function cb(node: ts.Node): void { + switch (node.kind) { + case ts.SyntaxKind.BinaryExpression: { + const binaryExpr = node as ts.BinaryExpression; + if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { + const leftIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.left), ts.TypeFlags.StringLike); + const rightIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.right), ts.TypeFlags.StringLike); + if ( + (leftIsString && !rightIsString) + || (!leftIsString && rightIsString) + ) { + addFailure(ctx, node); + } + } + break; + } + case ts.SyntaxKind.TemplateSpan: { + const templateSpanNode = node as ts.TemplateSpan; + const type = checker.getTypeAtLocation(templateSpanNode.expression); + const isString = isTypeFlagSet(type, ts.TypeFlags.StringLike); + if (!isString) { + addFailure(ctx, node); + } + break; + } + } + return ts.forEachChild(node, cb); + }); +} + +function addFailure (ctx: Lint.WalkContext, node: ts.Node) { + ctx.addFailureAtNode(node, 'Explicit conversion to string type required'); +} + +declare module "typescript" { + // No other way to distinguish boolean literal true from boolean literal false + export interface IntrinsicType extends ts.Type { + intrinsicName: string; + } +} diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint new file mode 100644 index 00000000000..0c48a83b294 --- /dev/null +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -0,0 +1,105 @@ + const fooStr: string = 'foo'; + const fooNumber = 2; + class FooClass {} + class ClassWithToString { + public static toString () { return ''; } + public toString () { return ''; } + } + const classWithToString = new ClassWithToString(); + const FooStr = new String('foo'); + const fooArr = ['foo']; + const emptyArr = []; + + `foo` + `${fooStr}` + `${fooNumber}` + ~~~~~~~~~~~ [Explicit conversion to string type required] + `${FooClass}` + ~~~~~~~~~~ [Explicit conversion to string type required] + `${ClassWithToString}` + ~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${classWithToString}` + ~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${FooStr}` + ~~~~~~~~ [Explicit conversion to string type required] + `${fooArr}` + ~~~~~~~~ [Explicit conversion to string type required] + `${emptyArr}` + ~~~~~~~~~~ [Explicit conversion to string type required] + + `${String(fooStr)}` + `${String(fooNumber)}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${fooStr.toString()}` + `${fooNumber.toString()}` + `${FooClass.toString()}` + `${ClassWithToString.toString()}` + `${classWithToString.toString()}` + `${FooStr.toString()}` + `${fooArr.toString()}` + `${emptyArr.toString()}` + + 'str' + fooStr + 'str' + 'str' + fooNumber + 'str' + ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + FooClass + 'str' + ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + ClassWithToString + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + classWithToString + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + FooStr + 'str' + ~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + fooArr + 'str' + ~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + emptyArr + 'str' + ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + + 'str' + String(fooStr) + 'str' + 'str' + String(fooNumber) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + fooStr.toString() + 'str' + 'str' + fooNumber.toString() + 'str' + 'str' + FooClass.toString() + 'str' + 'str' + ClassWithToString.toString() + 'str' + 'str' + classWithToString.toString() + 'str' + 'str' + FooStr.toString() + 'str' + 'str' + fooArr.toString() + 'str' + 'str' + emptyArr.toString() + 'str' + + const barFooStrOrUndef: string | undefined; + const barFooStrOrNull: string | null; + + `${barFooStrOrUndef}` + ~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${barFooStrOrNull}` + ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + + `${String(barFooStrOrUndef)}` + `${String(barFooStrOrNull)}` + + `${barFooStrOrUndef.toString()}` + `${barFooStrOrNull.toString()}` + + 'str' + barFooStrOrUndef + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + barFooStrOrNull + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + + 'str' + String(barFooStrOrUndef) + 'str' + 'str' + String(barFooStrOrNull) + 'str' + + 'str' + barFooStrOrUndef.toString() + 'str' + 'str' + barFooStrOrNull.toString() + 'str' diff --git a/test/rules/strict-string-expressions/tsconfig.json b/test/rules/strict-string-expressions/tsconfig.json new file mode 100644 index 00000000000..c4d430eb449 --- /dev/null +++ b/test/rules/strict-string-expressions/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strictNullChecks": true + } +} diff --git a/test/rules/strict-string-expressions/tslint.json b/test/rules/strict-string-expressions/tslint.json new file mode 100644 index 00000000000..dcd6ed572f2 --- /dev/null +++ b/test/rules/strict-string-expressions/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "strict-string-expressions": true + } +} From 928d5f7a00e5ae16edced95954092975aadf3bb8 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 00:26:25 +0300 Subject: [PATCH 02/27] docs(strictStringExpressionsRule): add option desc --- src/rules/strictStringExpressionsRule.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 28142e6997e..09f42632577 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -34,7 +34,8 @@ export class Rule extends Lint.Rules.TypedRule { typescriptOnly: true, requiresTypeInfo: true, options: [], - optionsDescription: '', + optionExamples: [true], + optionsDescription: "Not configurable." }; public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { From fda5ea422440b6e0615174f66a000029758bcf0d Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 00:53:42 +0300 Subject: [PATCH 03/27] feat(strictStringExpressionsRule): add fixer --- src/rules/strictStringExpressionsRule.ts | 34 +++++--- .../strict-string-expressions/test.ts.fix | 87 +++++++++++++++++++ 2 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 test/rules/strict-string-expressions/test.ts.fix diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 09f42632577..fdd83685c84 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -23,7 +23,8 @@ import { isTypeFlagSet } from 'tsutils'; export class Rule extends Lint.Rules.TypedRule { public static metadata: Lint.IRuleMetadata = { ruleName: "strict-string-expressions", - description: Lint.Utils.dedent` + description: 'Disable implicit toString() calls', + descriptionDetails: Lint.Utils.dedent` Require explicit toString() call for variables used in strings. By default only strings are allowed. The following nodes are checked: @@ -35,9 +36,12 @@ export class Rule extends Lint.Rules.TypedRule { requiresTypeInfo: true, options: [], optionExamples: [true], - optionsDescription: "Not configurable." + optionsDescription: "Not configurable.", + hasFix: true }; + public static CONVERSION_REQUIRED = 'Explicit conversion to string type required'; + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); } @@ -52,11 +56,16 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { const leftIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.left), ts.TypeFlags.StringLike); const rightIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.right), ts.TypeFlags.StringLike); - if ( - (leftIsString && !rightIsString) - || (!leftIsString && rightIsString) - ) { - addFailure(ctx, node); + const leftIsFailed = !leftIsString && rightIsString; + const rightIsFailed = leftIsString && !rightIsString; + if (leftIsFailed || rightIsFailed) { + const expression = leftIsFailed ? binaryExpr.left : binaryExpr.right; + const fix = Lint.Replacement.replaceFromTo( + expression.getStart(sourceFile), + expression.end, + `String(${expression.getText()})` + ); + ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); } } break; @@ -66,7 +75,13 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { const type = checker.getTypeAtLocation(templateSpanNode.expression); const isString = isTypeFlagSet(type, ts.TypeFlags.StringLike); if (!isString) { - addFailure(ctx, node); + const { expression } = templateSpanNode; + const fix = Lint.Replacement.replaceFromTo( + expression.getStart(sourceFile), + expression.end, + `String(${expression.getText()})` + ); + ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); } break; } @@ -75,9 +90,6 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { }); } -function addFailure (ctx: Lint.WalkContext, node: ts.Node) { - ctx.addFailureAtNode(node, 'Explicit conversion to string type required'); -} declare module "typescript" { // No other way to distinguish boolean literal true from boolean literal false diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix new file mode 100644 index 00000000000..5f85a633723 --- /dev/null +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -0,0 +1,87 @@ + const fooStr: string = 'foo'; + const fooNumber = 2; + class FooClass {} + class ClassWithToString { + public static toString () { return ''; } + public toString () { return ''; } + } + const classWithToString = new ClassWithToString(); + const FooStr = new String('foo'); + const fooArr = ['foo']; + const emptyArr = []; + + `foo` + `${fooStr}` + `${String(fooNumber)}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${String(fooStr)}` + `${String(fooNumber)}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${fooStr.toString()}` + `${fooNumber.toString()}` + `${FooClass.toString()}` + `${ClassWithToString.toString()}` + `${classWithToString.toString()}` + `${FooStr.toString()}` + `${fooArr.toString()}` + `${emptyArr.toString()}` + + 'str' + fooStr + 'str' + 'str' + String(fooNumber) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + String(fooStr) + 'str' + 'str' + String(fooNumber) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + fooStr.toString() + 'str' + 'str' + fooNumber.toString() + 'str' + 'str' + FooClass.toString() + 'str' + 'str' + ClassWithToString.toString() + 'str' + 'str' + classWithToString.toString() + 'str' + 'str' + FooStr.toString() + 'str' + 'str' + fooArr.toString() + 'str' + 'str' + emptyArr.toString() + 'str' + + const barFooStrOrUndef: string | undefined; + const barFooStrOrNull: string | null; + + `${String(barFooStrOrUndef)}` + `${String(barFooStrOrNull)}` + + `${String(barFooStrOrUndef)}` + `${String(barFooStrOrNull)}` + + `${barFooStrOrUndef.toString()}` + `${barFooStrOrNull.toString()}` + + 'str' + String(barFooStrOrUndef) + 'str' + 'str' + String(barFooStrOrNull) + 'str' + + 'str' + String(barFooStrOrUndef) + 'str' + 'str' + String(barFooStrOrNull) + 'str' + + 'str' + barFooStrOrUndef.toString() + 'str' + 'str' + barFooStrOrNull.toString() + 'str' From f7ddea60b1bfe8ea09010dccb566331772bb1594 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 00:57:03 +0300 Subject: [PATCH 04/27] test: add strict-string-expressions into all.ts --- src/configs/all.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/configs/all.ts b/src/configs/all.ts index cc6303fc945..2304996aeb1 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -150,6 +150,7 @@ export const rules = { "restrict-plus-operands": true, "static-this": true, "strict-boolean-expressions": true, + "strict-string-expressions": true, "strict-comparisons": true, "strict-type-predicates": true, "switch-default": true, From b2f9bc513431ffdc96b168ee3669e28bfcee06cc Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 00:59:15 +0300 Subject: [PATCH 05/27] type: remove declare --- src/rules/strictStringExpressionsRule.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index fdd83685c84..553d85607bb 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -89,11 +89,3 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { return ts.forEachChild(node, cb); }); } - - -declare module "typescript" { - // No other way to distinguish boolean literal true from boolean literal false - export interface IntrinsicType extends ts.Type { - intrinsicName: string; - } -} From e8a809a1246ed83439635bb868f155f3cf7c63b3 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:02:32 +0300 Subject: [PATCH 06/27] refactor(strictStringExpressionsRule): eliminate copy-paste --- src/rules/strictStringExpressionsRule.ts | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 553d85607bb..ff2321a5d57 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -60,12 +60,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { const rightIsFailed = leftIsString && !rightIsString; if (leftIsFailed || rightIsFailed) { const expression = leftIsFailed ? binaryExpr.left : binaryExpr.right; - const fix = Lint.Replacement.replaceFromTo( - expression.getStart(sourceFile), - expression.end, - `String(${expression.getText()})` - ); - ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); + addFailure(binaryExpr, expression); } } break; @@ -76,16 +71,23 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { const isString = isTypeFlagSet(type, ts.TypeFlags.StringLike); if (!isString) { const { expression } = templateSpanNode; - const fix = Lint.Replacement.replaceFromTo( - expression.getStart(sourceFile), - expression.end, - `String(${expression.getText()})` - ); - ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); + addFailure(templateSpanNode, expression); } break; } } return ts.forEachChild(node, cb); }); + + function addFailure ( + node: ts.Node, + expression: ts.Expression, + ) { + const fix = Lint.Replacement.replaceFromTo( + expression.getStart(), + expression.end, + `String(${expression.getText()})` + ); + ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); + } } From 5a9e7daec4d1fb780829cf28997a222d521e420c Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:16:29 +0300 Subject: [PATCH 07/27] style: fix tslint errors --- src/rules/strictStringExpressionsRule.ts | 38 +++++++++++++----------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index ff2321a5d57..5d0b770b2f7 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -15,15 +15,14 @@ * limitations under the License. */ +import { isTypeFlagSet } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; -import { isTypeFlagSet } from 'tsutils'; export class Rule extends Lint.Rules.TypedRule { public static metadata: Lint.IRuleMetadata = { - ruleName: "strict-string-expressions", - description: 'Disable implicit toString() calls', + description: "Disable implicit toString() calls", descriptionDetails: Lint.Utils.dedent` Require explicit toString() call for variables used in strings. By default only strings are allowed. @@ -31,31 +30,38 @@ export class Rule extends Lint.Rules.TypedRule { * String literals ("foo" + bar) * ES6 templates (\`foo \${bar}\`)`, - type: "functionality", - typescriptOnly: true, - requiresTypeInfo: true, - options: [], + hasFix: true, optionExamples: [true], + options: [], optionsDescription: "Not configurable.", - hasFix: true + requiresTypeInfo: true, + ruleName: "strict-string-expressions", + type: "functionality", + typescriptOnly: true, }; - public static CONVERSION_REQUIRED = 'Explicit conversion to string type required'; + public static CONVERSION_REQUIRED = "Explicit conversion to string type required"; public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); } } -function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { +function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { const { sourceFile } = ctx; ts.forEachChild(sourceFile, function cb(node: ts.Node): void { switch (node.kind) { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.left), ts.TypeFlags.StringLike); - const rightIsString = isTypeFlagSet(checker.getTypeAtLocation(binaryExpr.right), ts.TypeFlags.StringLike); + const leftIsString = isTypeFlagSet( + checker.getTypeAtLocation(binaryExpr.left), + ts.TypeFlags.StringLike, + ); + const rightIsString = isTypeFlagSet( + checker.getTypeAtLocation(binaryExpr.right), + ts.TypeFlags.StringLike, + ); const leftIsFailed = !leftIsString && rightIsString; const rightIsFailed = leftIsString && !rightIsString; if (leftIsFailed || rightIsFailed) { @@ -73,20 +79,16 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); } - break; } } return ts.forEachChild(node, cb); }); - function addFailure ( - node: ts.Node, - expression: ts.Expression, - ) { + function addFailure(node: ts.Node, expression: ts.Expression) { const fix = Lint.Replacement.replaceFromTo( expression.getStart(), expression.end, - `String(${expression.getText()})` + `String(${expression.getText()})`, ); ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); } From fa07d78926100adc4621835a3eb7436cdf9e338a Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:27:11 +0300 Subject: [PATCH 08/27] fix(strictStringExpressionsRule): exclude any type from checks --- src/rules/strictStringExpressionsRule.ts | 16 +++++++--------- test/rules/strict-string-expressions/test.ts.fix | 7 +++++++ .../rules/strict-string-expressions/test.ts.lint | 7 +++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 5d0b770b2f7..94f9aa48117 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -54,14 +54,8 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsString = isTypeFlagSet( - checker.getTypeAtLocation(binaryExpr.left), - ts.TypeFlags.StringLike, - ); - const rightIsString = isTypeFlagSet( - checker.getTypeAtLocation(binaryExpr.right), - ts.TypeFlags.StringLike, - ); + const leftIsString = isTypeRequiresExplicitToString(checker.getTypeAtLocation(binaryExpr.left)); + const rightIsString = isTypeRequiresExplicitToString(checker.getTypeAtLocation(binaryExpr.right)); const leftIsFailed = !leftIsString && rightIsString; const rightIsFailed = leftIsString && !rightIsString; if (leftIsFailed || rightIsFailed) { @@ -74,7 +68,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.TemplateSpan: { const templateSpanNode = node as ts.TemplateSpan; const type = checker.getTypeAtLocation(templateSpanNode.expression); - const isString = isTypeFlagSet(type, ts.TypeFlags.StringLike); + const isString = isTypeRequiresExplicitToString(type); if (!isString) { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); @@ -93,3 +87,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { ctx.addFailureAtNode(node, Rule.CONVERSION_REQUIRED, fix); } } + +function isTypeRequiresExplicitToString (type: ts.Type) { + return isTypeFlagSet(type, ts.TypeFlags.StringLike) || isTypeFlagSet(type, ts.TypeFlags.Any); +} diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index 5f85a633723..6d1a0af7c4f 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -1,3 +1,4 @@ + const fooAny: any; const fooStr: string = 'foo'; const fooNumber = 2; class FooClass {} @@ -11,6 +12,7 @@ const emptyArr = []; `foo` + `${fooAny}` `${fooStr}` `${String(fooNumber)}` `${String(FooClass)}` @@ -20,6 +22,7 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${String(fooAny)}` `${String(fooStr)}` `${String(fooNumber)}` `${String(FooClass)}` @@ -29,6 +32,7 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${fooAny.toString()}` `${fooStr.toString()}` `${fooNumber.toString()}` `${FooClass.toString()}` @@ -38,6 +42,7 @@ `${fooArr.toString()}` `${emptyArr.toString()}` + 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' @@ -47,6 +52,7 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' @@ -56,6 +62,7 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + fooNumber.toString() + 'str' 'str' + FooClass.toString() + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint index 0c48a83b294..9de85574f7d 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -1,3 +1,4 @@ + const fooAny: any; const fooStr: string = 'foo'; const fooNumber = 2; class FooClass {} @@ -11,6 +12,7 @@ const emptyArr = []; `foo` + `${fooAny}` `${fooStr}` `${fooNumber}` ~~~~~~~~~~~ [Explicit conversion to string type required] @@ -27,6 +29,7 @@ `${emptyArr}` ~~~~~~~~~~ [Explicit conversion to string type required] + `${String(fooAny)}` `${String(fooStr)}` `${String(fooNumber)}` `${String(FooClass)}` @@ -36,6 +39,7 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${fooAny.toString()}` `${fooStr.toString()}` `${fooNumber.toString()}` `${FooClass.toString()}` @@ -45,6 +49,7 @@ `${fooArr.toString()}` `${emptyArr.toString()}` + 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + fooNumber + 'str' ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] @@ -61,6 +66,7 @@ 'str' + emptyArr + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' @@ -70,6 +76,7 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + fooNumber.toString() + 'str' 'str' + FooClass.toString() + 'str' From fe6332dd67cb64e482313c9a85f447898949fbeb Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:32:30 +0300 Subject: [PATCH 09/27] style: fix sytle erorrs --- src/rules/strictStringExpressionsRule.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 94f9aa48117..e7eb8962301 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -54,8 +54,12 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsString = isTypeRequiresExplicitToString(checker.getTypeAtLocation(binaryExpr.left)); - const rightIsString = isTypeRequiresExplicitToString(checker.getTypeAtLocation(binaryExpr.right)); + const leftIsString = isTypeRequiresExplicitToString( + checker.getTypeAtLocation(binaryExpr.left), + ); + const rightIsString = isTypeRequiresExplicitToString( + checker.getTypeAtLocation(binaryExpr.right), + ); const leftIsFailed = !leftIsString && rightIsString; const rightIsFailed = leftIsString && !rightIsString; if (leftIsFailed || rightIsFailed) { @@ -88,6 +92,6 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } } -function isTypeRequiresExplicitToString (type: ts.Type) { +function isTypeRequiresExplicitToString(type: ts.Type) { return isTypeFlagSet(type, ts.TypeFlags.StringLike) || isTypeFlagSet(type, ts.TypeFlags.Any); } From 643268cec4cab7fb1949a5db8973cda49a40302e Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:35:45 +0300 Subject: [PATCH 10/27] refactor: rename function name to more cohesive --- src/rules/strictStringExpressionsRule.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index e7eb8962301..1dab734c213 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -54,10 +54,10 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsString = isTypeRequiresExplicitToString( + const leftIsString = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.left), ); - const rightIsString = isTypeRequiresExplicitToString( + const rightIsString = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.right), ); const leftIsFailed = !leftIsString && rightIsString; @@ -72,7 +72,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.TemplateSpan: { const templateSpanNode = node as ts.TemplateSpan; const type = checker.getTypeAtLocation(templateSpanNode.expression); - const isString = isTypeRequiresExplicitToString(type); + const isString = isTypeConvertsToStringEasily(type); if (!isString) { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); @@ -92,6 +92,6 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } } -function isTypeRequiresExplicitToString(type: ts.Type) { +function isTypeConvertsToStringEasily(type: ts.Type) { return isTypeFlagSet(type, ts.TypeFlags.StringLike) || isTypeFlagSet(type, ts.TypeFlags.Any); } From 2fafc18418b9c87aaca15ddf6c952c1b965d2e09 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:43:45 +0300 Subject: [PATCH 11/27] refactor(strictStringExpressionsRule): add more renaimings --- src/rules/strictStringExpressionsRule.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 1dab734c213..4cc2f718b13 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -54,14 +54,14 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsString = isTypeConvertsToStringEasily( + const leftIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.left), ); - const rightIsString = isTypeConvertsToStringEasily( + const rightIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.right), ); - const leftIsFailed = !leftIsString && rightIsString; - const rightIsFailed = leftIsString && !rightIsString; + const leftIsFailed = !leftIsPassedAsIs && rightIsPassedAsIs; + const rightIsFailed = leftIsPassedAsIs && !rightIsPassedAsIs; if (leftIsFailed || rightIsFailed) { const expression = leftIsFailed ? binaryExpr.left : binaryExpr.right; addFailure(binaryExpr, expression); @@ -72,8 +72,8 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.TemplateSpan: { const templateSpanNode = node as ts.TemplateSpan; const type = checker.getTypeAtLocation(templateSpanNode.expression); - const isString = isTypeConvertsToStringEasily(type); - if (!isString) { + const shouldPassAsIs = isTypeConvertsToStringEasily(type); + if (!shouldPassAsIs) { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); } From 217229f788e5c2ab9506e63e066dabd81bbfe3dc Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:46:44 +0300 Subject: [PATCH 12/27] style(strictStringExpressionsRule): prettify --- src/rules/strictStringExpressionsRule.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 4cc2f718b13..4f514af11ad 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -93,5 +93,10 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } function isTypeConvertsToStringEasily(type: ts.Type) { - return isTypeFlagSet(type, ts.TypeFlags.StringLike) || isTypeFlagSet(type, ts.TypeFlags.Any); + return ( + isTypeFlagSet(type, ts.TypeFlags.StringOrNumberLiteral) || + isTypeFlagSet(type, ts.TypeFlags.NumberLike) || + isTypeFlagSet(type, ts.TypeFlags.StringLike) || + isTypeFlagSet(type, ts.TypeFlags.Any) + ); } From 235d7aa311181c5b10264120b5fabfb4f80548d4 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:47:15 +0300 Subject: [PATCH 13/27] fix: do not require numbers to be stringified --- test/rules/strict-string-expressions/test.ts.fix | 8 ++++---- test/rules/strict-string-expressions/test.ts.lint | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index 6d1a0af7c4f..c16d02d345b 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -14,7 +14,7 @@ `foo` `${fooAny}` `${fooStr}` - `${String(fooNumber)}` + `${fooNumber}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -24,7 +24,7 @@ `${String(fooAny)}` `${String(fooStr)}` - `${String(fooNumber)}` + `${fooNumber}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -44,7 +44,7 @@ 'str' + fooAny + 'str' 'str' + fooStr + 'str' - 'str' + String(fooNumber) + 'str' + 'str' + fooNumber + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' @@ -54,7 +54,7 @@ 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' - 'str' + String(fooNumber) + 'str' + 'str' + fooNumber + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint index 9de85574f7d..7a0756c4ecd 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -15,7 +15,6 @@ `${fooAny}` `${fooStr}` `${fooNumber}` - ~~~~~~~~~~~ [Explicit conversion to string type required] `${FooClass}` ~~~~~~~~~~ [Explicit conversion to string type required] `${ClassWithToString}` @@ -52,7 +51,6 @@ 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + fooNumber + 'str' - ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] 'str' + FooClass + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] 'str' + ClassWithToString + 'str' From 589f06f8873517a19c972c126fb49a246501457f Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:49:11 +0300 Subject: [PATCH 14/27] test(strictStringExpressions): add string and number literals --- test/rules/strict-string-expressions/test.ts.fix | 12 ++++++++++++ test/rules/strict-string-expressions/test.ts.lint | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index c16d02d345b..38b87294aeb 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -12,6 +12,8 @@ const emptyArr = []; `foo` + `${'str literal'}` + `${123}` `${fooAny}` `${fooStr}` `${fooNumber}` @@ -22,6 +24,8 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${String('str literal')}` + `${String(123)}` `${String(fooAny)}` `${String(fooStr)}` `${fooNumber}` @@ -32,6 +36,8 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${'str literal'.toString()}` + `${123..toString()}` `${fooAny.toString()}` `${fooStr.toString()}` `${fooNumber.toString()}` @@ -42,6 +48,8 @@ `${fooArr.toString()}` `${emptyArr.toString()}` + 'str' + 'str literal' + 'str' + 'str' + 123 + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + fooNumber + 'str' @@ -52,6 +60,8 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + String('str literal') + 'str' + 'str' + String(123) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + fooNumber + 'str' @@ -62,6 +72,8 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + 'str literal'.toString() + 'str' + 'str' + 123..toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + fooNumber.toString() + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint index 7a0756c4ecd..2c074240625 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -12,6 +12,8 @@ const emptyArr = []; `foo` + `${'str literal'}` + `${123}` `${fooAny}` `${fooStr}` `${fooNumber}` @@ -28,6 +30,8 @@ `${emptyArr}` ~~~~~~~~~~ [Explicit conversion to string type required] + `${String('str literal')}` + `${String(123)}` `${String(fooAny)}` `${String(fooStr)}` `${String(fooNumber)}` @@ -38,6 +42,8 @@ `${String(fooArr)}` `${String(emptyArr)}` + `${'str literal'.toString()}` + `${123..toString()}` `${fooAny.toString()}` `${fooStr.toString()}` `${fooNumber.toString()}` @@ -48,6 +54,8 @@ `${fooArr.toString()}` `${emptyArr.toString()}` + 'str' + 'str literal' + 'str' + 'str' + 123 + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + fooNumber + 'str' @@ -64,6 +72,8 @@ 'str' + emptyArr + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + String('str literal') + 'str' + 'str' + String(123) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + String(fooNumber) + 'str' @@ -74,6 +84,8 @@ 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + 'str literal'.toString() + 'str' + 'str' + 123..toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + fooNumber.toString() + 'str' From 6259432446c48136fbe8e3e9251afca2d3665a8a Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 01:56:42 +0300 Subject: [PATCH 15/27] test(strictStringExpressionsRule): fix nit --- test/rules/strict-string-expressions/test.ts.fix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index 38b87294aeb..6709416a370 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -28,7 +28,7 @@ `${String(123)}` `${String(fooAny)}` `${String(fooStr)}` - `${fooNumber}` + `${String(fooNumber)}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -64,7 +64,7 @@ 'str' + String(123) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' - 'str' + fooNumber + 'str' + 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' From 88a227847312a673110fdb49289390e3a5c7b88d Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Tue, 23 Jul 2019 02:02:04 +0300 Subject: [PATCH 16/27] test(strictStringExpressionsRule): add string uni in test --- test/rules/strict-string-expressions/test.ts.fix | 7 +++++++ test/rules/strict-string-expressions/test.ts.lint | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index 6709416a370..6c2d16ecf29 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -10,12 +10,14 @@ const FooStr = new String('foo'); const fooArr = ['foo']; const emptyArr = []; + const stringUni = "foo" | "bar"; `foo` `${'str literal'}` `${123}` `${fooAny}` `${fooStr}` + `${stringUni}` `${fooNumber}` `${String(FooClass)}` `${String(ClassWithToString)}` @@ -28,6 +30,7 @@ `${String(123)}` `${String(fooAny)}` `${String(fooStr)}` + `${String(stringUni)}` `${String(fooNumber)}` `${String(FooClass)}` `${String(ClassWithToString)}` @@ -40,6 +43,7 @@ `${123..toString()}` `${fooAny.toString()}` `${fooStr.toString()}` + `${stringUni.toString()}` `${fooNumber.toString()}` `${FooClass.toString()}` `${ClassWithToString.toString()}` @@ -52,6 +56,7 @@ 'str' + 123 + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' + 'str' + stringUni + 'str' 'str' + fooNumber + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' @@ -64,6 +69,7 @@ 'str' + String(123) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' + 'str' + String(stringUni) + 'str' 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' @@ -76,6 +82,7 @@ 'str' + 123..toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' + 'str' + stringUni.toString() + 'str' 'str' + fooNumber.toString() + 'str' 'str' + FooClass.toString() + 'str' 'str' + ClassWithToString.toString() + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint index 2c074240625..7674bbef27b 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -10,12 +10,14 @@ const FooStr = new String('foo'); const fooArr = ['foo']; const emptyArr = []; + const stringUni = "foo" | "bar"; `foo` `${'str literal'}` `${123}` `${fooAny}` `${fooStr}` + `${stringUni}` `${fooNumber}` `${FooClass}` ~~~~~~~~~~ [Explicit conversion to string type required] @@ -34,6 +36,7 @@ `${String(123)}` `${String(fooAny)}` `${String(fooStr)}` + `${String(stringUni)}` `${String(fooNumber)}` `${String(FooClass)}` `${String(ClassWithToString)}` @@ -46,6 +49,7 @@ `${123..toString()}` `${fooAny.toString()}` `${fooStr.toString()}` + `${stringUni.toString()}` `${fooNumber.toString()}` `${FooClass.toString()}` `${ClassWithToString.toString()}` @@ -58,6 +62,7 @@ 'str' + 123 + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' + 'str' + stringUni + 'str' 'str' + fooNumber + 'str' 'str' + FooClass + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] @@ -76,6 +81,7 @@ 'str' + String(123) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' + 'str' + String(stringUni) + 'str' 'str' + String(fooNumber) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' @@ -88,6 +94,7 @@ 'str' + 123..toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' + 'str' + stringUni.toString() + 'str' 'str' + fooNumber.toString() + 'str' 'str' + FooClass.toString() + 'str' 'str' + ClassWithToString.toString() + 'str' From 7d8253d6ce50e646ce053d1ac3a2717fee3eba74 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 20:49:22 +0300 Subject: [PATCH 17/27] test: add typeof window case --- test/rules/strict-string-expressions/test.ts.fix | 6 ++++++ test/rules/strict-string-expressions/test.ts.lint | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/test.ts.fix index 6c2d16ecf29..8c37b644dc6 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/test.ts.fix @@ -19,6 +19,7 @@ `${fooStr}` `${stringUni}` `${fooNumber}` + `${(typeof window)}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -32,6 +33,7 @@ `${String(fooStr)}` `${String(stringUni)}` `${String(fooNumber)}` + `${String((typeof window))}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -45,6 +47,7 @@ `${fooStr.toString()}` `${stringUni.toString()}` `${fooNumber.toString()}` + `${(typeof window).toString()}` `${FooClass.toString()}` `${ClassWithToString.toString()}` `${classWithToString.toString()}` @@ -58,6 +61,7 @@ 'str' + fooStr + 'str' 'str' + stringUni + 'str' 'str' + fooNumber + 'str' + 'str' + (typeof window) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' @@ -71,6 +75,7 @@ 'str' + String(fooStr) + 'str' 'str' + String(stringUni) + 'str' 'str' + String(fooNumber) + 'str' + 'str' + String((typeof window)) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' @@ -84,6 +89,7 @@ 'str' + fooStr.toString() + 'str' 'str' + stringUni.toString() + 'str' 'str' + fooNumber.toString() + 'str' + 'str' + (typeof window).toString() + 'str' 'str' + FooClass.toString() + 'str' 'str' + ClassWithToString.toString() + 'str' 'str' + classWithToString.toString() + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/test.ts.lint index 7674bbef27b..b9c0c0cd988 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/test.ts.lint @@ -19,6 +19,7 @@ `${fooStr}` `${stringUni}` `${fooNumber}` + `${(typeof window)}` `${FooClass}` ~~~~~~~~~~ [Explicit conversion to string type required] `${ClassWithToString}` @@ -38,6 +39,7 @@ `${String(fooStr)}` `${String(stringUni)}` `${String(fooNumber)}` + `${String((typeof window))}` `${String(FooClass)}` `${String(ClassWithToString)}` `${String(classWithToString)}` @@ -51,6 +53,7 @@ `${fooStr.toString()}` `${stringUni.toString()}` `${fooNumber.toString()}` + `${(typeof window).toString()}` `${FooClass.toString()}` `${ClassWithToString.toString()}` `${classWithToString.toString()}` @@ -64,6 +67,7 @@ 'str' + fooStr + 'str' 'str' + stringUni + 'str' 'str' + fooNumber + 'str' + 'str' + (typeof window) + 'str' 'str' + FooClass + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] 'str' + ClassWithToString + 'str' @@ -83,6 +87,7 @@ 'str' + String(fooStr) + 'str' 'str' + String(stringUni) + 'str' 'str' + String(fooNumber) + 'str' + 'str' + String((typeof window)) + 'str' 'str' + String(FooClass) + 'str' 'str' + String(ClassWithToString) + 'str' 'str' + String(classWithToString) + 'str' @@ -96,6 +101,7 @@ 'str' + fooStr.toString() + 'str' 'str' + stringUni.toString() + 'str' 'str' + fooNumber.toString() + 'str' + 'str' + (typeof window).toString() + 'str' 'str' + FooClass.toString() + 'str' 'str' + ClassWithToString.toString() + 'str' 'str' + classWithToString.toString() + 'str' From ae1c2be586a3ebaf636e182a22b25a66a8a952bd Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 20:54:10 +0300 Subject: [PATCH 18/27] feat: handle union type --- src/rules/strictStringExpressionsRule.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 4f514af11ad..4194806bb7c 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { isTypeFlagSet } from "tsutils"; +import { isTypeFlagSet, isUnionType } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -93,6 +93,10 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } function isTypeConvertsToStringEasily(type: ts.Type) { + if (isUnionType(type)) { + return type.types.every(isTypeConvertsToStringEasily); + } + return ( isTypeFlagSet(type, ts.TypeFlags.StringOrNumberLiteral) || isTypeFlagSet(type, ts.TypeFlags.NumberLike) || From 7de511bb37e3eae9eeddab62875c27188720ddbe Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 21:29:37 +0300 Subject: [PATCH 19/27] feat(strictStringExpressionsRule): add allow-empty-types option --- src/rules/strictStringExpressionsRule.ts | 64 ++++++-- .../allow-empty-types/test.ts.fix | 126 +++++++++++++++ .../{ => allow-empty-types}/test.ts.lint | 11 +- .../allow-empty-types/tsconfig.json | 4 + .../allow-empty-types/tslint.json | 5 + .../{ => default}/test.ts.fix | 7 + .../default/test.ts.lint | 144 ++++++++++++++++++ .../{ => default}/tsconfig.json | 0 .../{ => default}/tslint.json | 0 9 files changed, 346 insertions(+), 15 deletions(-) create mode 100644 test/rules/strict-string-expressions/allow-empty-types/test.ts.fix rename test/rules/strict-string-expressions/{ => allow-empty-types}/test.ts.lint (93%) create mode 100644 test/rules/strict-string-expressions/allow-empty-types/tsconfig.json create mode 100644 test/rules/strict-string-expressions/allow-empty-types/tslint.json rename test/rules/strict-string-expressions/{ => default}/test.ts.fix (93%) create mode 100644 test/rules/strict-string-expressions/default/test.ts.lint rename test/rules/strict-string-expressions/{ => default}/tsconfig.json (100%) rename test/rules/strict-string-expressions/{ => default}/tslint.json (100%) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 4194806bb7c..74bbfa06ae8 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -20,7 +20,15 @@ import * as ts from "typescript"; import * as Lint from "../index"; +const OPTION_ALLOW_EMPTY_TYPES = 'allow-empty-types'; + +interface Options { + [OPTION_ALLOW_EMPTY_TYPES]?: boolean; +} + export class Rule extends Lint.Rules.TypedRule { + public static CONVERSION_REQUIRED = "Explicit conversion to string type required"; + public static metadata: Lint.IRuleMetadata = { description: "Disable implicit toString() calls", descriptionDetails: Lint.Utils.dedent` @@ -31,24 +39,47 @@ export class Rule extends Lint.Rules.TypedRule { * String literals ("foo" + bar) * ES6 templates (\`foo \${bar}\`)`, hasFix: true, - optionExamples: [true], - options: [], - optionsDescription: "Not configurable.", + optionsDescription: Lint.Utils.dedent` + Following arguments may be optionally provided: + * \`${OPTION_ALLOW_EMPTY_TYPES}\` allows \`null\`, \`undefined\` and \`never\` to be passed into strings without explicit conversion`, + options: { + type: "object", + properties: { + [OPTION_ALLOW_EMPTY_TYPES]: { + type: "boolean", + }, + }, + }, + optionExamples: [ + true, + [ + true, + { + [OPTION_ALLOW_EMPTY_TYPES]: true, + }, + ], + ], requiresTypeInfo: true, ruleName: "strict-string-expressions", type: "functionality", typescriptOnly: true, }; - public static CONVERSION_REQUIRED = "Explicit conversion to string type required"; - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); + return this.applyWithFunction(sourceFile, walk, this.getRuleOptions(), program.getTypeChecker()); + } + + private getRuleOptions(): Options { + if (this.ruleArguments[0] === undefined) { + return {}; + } else { + return this.ruleArguments[0] as Options; + } } } -function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { - const { sourceFile } = ctx; +function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { + const { sourceFile, options } = ctx; ts.forEachChild(sourceFile, function cb(node: ts.Node): void { switch (node.kind) { case ts.SyntaxKind.BinaryExpression: { @@ -56,9 +87,11 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { const leftIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.left), + options ); const rightIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.right), + options ); const leftIsFailed = !leftIsPassedAsIs && rightIsPassedAsIs; const rightIsFailed = leftIsPassedAsIs && !rightIsPassedAsIs; @@ -72,7 +105,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.TemplateSpan: { const templateSpanNode = node as ts.TemplateSpan; const type = checker.getTypeAtLocation(templateSpanNode.expression); - const shouldPassAsIs = isTypeConvertsToStringEasily(type); + const shouldPassAsIs = isTypeConvertsToStringEasily(type, options); if (!shouldPassAsIs) { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); @@ -92,9 +125,18 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } } -function isTypeConvertsToStringEasily(type: ts.Type) { +const isEmptyType = (type: ts.Type): boolean => isTypeFlagSet(type, ts.TypeFlags.Null) || + isTypeFlagSet(type, ts.TypeFlags.VoidLike) || + isTypeFlagSet(type, ts.TypeFlags.Undefined) || + isTypeFlagSet(type, ts.TypeFlags.Never); + +function isTypeConvertsToStringEasily(type: ts.Type, options: Options): boolean { if (isUnionType(type)) { - return type.types.every(isTypeConvertsToStringEasily); + return type.types.every((unionAtomicType) => isTypeConvertsToStringEasily(unionAtomicType, options)); + } + + if (options[OPTION_ALLOW_EMPTY_TYPES] && isEmptyType(type)) { + return true; } return ( diff --git a/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix b/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix new file mode 100644 index 00000000000..2e383f7bef3 --- /dev/null +++ b/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix @@ -0,0 +1,126 @@ + const fooAny: any; + const fooStr: string = 'foo'; + const fooNumber = 2; + class FooClass {} + class ClassWithToString { + public static toString () { return ''; } + public toString () { return ''; } + } + const classWithToString = new ClassWithToString(); + const FooStr = new String('foo'); + const fooArr = ['foo']; + const emptyArr = []; + const stringUni = "foo" | "bar"; + + `foo` + `${'str literal'}` + `${123}` + `${fooAny}` + `${fooStr}` + `${stringUni}` + `${fooNumber}` + `${(typeof window)}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${String('str literal')}` + `${String(123)}` + `${String(fooAny)}` + `${String(fooStr)}` + `${String(stringUni)}` + `${String(fooNumber)}` + `${String((typeof window))}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${'str literal'.toString()}` + `${123..toString()}` + `${fooAny.toString()}` + `${fooStr.toString()}` + `${stringUni.toString()}` + `${fooNumber.toString()}` + `${(typeof window).toString()}` + `${FooClass.toString()}` + `${ClassWithToString.toString()}` + `${classWithToString.toString()}` + `${FooStr.toString()}` + `${fooArr.toString()}` + `${emptyArr.toString()}` + + 'str' + 'str literal' + 'str' + 'str' + 123 + 'str' + 'str' + fooAny + 'str' + 'str' + fooStr + 'str' + 'str' + stringUni + 'str' + 'str' + fooNumber + 'str' + 'str' + (typeof window) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + String('str literal') + 'str' + 'str' + String(123) + 'str' + 'str' + String(fooAny) + 'str' + 'str' + String(fooStr) + 'str' + 'str' + String(stringUni) + 'str' + 'str' + String(fooNumber) + 'str' + 'str' + String((typeof window)) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + 'str literal'.toString() + 'str' + 'str' + 123..toString() + 'str' + 'str' + fooAny.toString() + 'str' + 'str' + fooStr.toString() + 'str' + 'str' + stringUni.toString() + 'str' + 'str' + fooNumber.toString() + 'str' + 'str' + (typeof window).toString() + 'str' + 'str' + FooClass.toString() + 'str' + 'str' + ClassWithToString.toString() + 'str' + 'str' + classWithToString.toString() + 'str' + 'str' + FooStr.toString() + 'str' + 'str' + fooArr.toString() + 'str' + 'str' + emptyArr.toString() + 'str' + + const barFooStrOrUndef: string | undefined; + const barFooStrOrNull: string | null; + const neverType: never; + + `${barFooStrOrUndef}` + `${barFooStrOrNull}` + `${neverType}` + + `${String(barFooStrOrUndef)}` + `${String(barFooStrOrNull)}` + `${String(neverType)}` + + `${barFooStrOrUndef.toString()}` + `${barFooStrOrNull.toString()}` + `${neverType.toString()}` + + 'str' + barFooStrOrUndef + 'str' + 'str' + barFooStrOrNull + 'str' + 'str' + neverType + 'str' + + 'str' + String(barFooStrOrUndef) + 'str' + 'str' + String(barFooStrOrNull) + 'str' + 'str' + String(neverType) + 'str' + + 'str' + barFooStrOrUndef.toString() + 'str' + 'str' + barFooStrOrNull.toString() + 'str' + 'str' + neverType.toString() + 'str' diff --git a/test/rules/strict-string-expressions/test.ts.lint b/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint similarity index 93% rename from test/rules/strict-string-expressions/test.ts.lint rename to test/rules/strict-string-expressions/allow-empty-types/test.ts.lint index b9c0c0cd988..9008e793079 100644 --- a/test/rules/strict-string-expressions/test.ts.lint +++ b/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint @@ -111,25 +111,28 @@ const barFooStrOrUndef: string | undefined; const barFooStrOrNull: string | null; + const neverType: never; `${barFooStrOrUndef}` - ~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] `${barFooStrOrNull}` - ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${neverType}` `${String(barFooStrOrUndef)}` `${String(barFooStrOrNull)}` + `${String(neverType)}` `${barFooStrOrUndef.toString()}` `${barFooStrOrNull.toString()}` + `${neverType.toString()}` 'str' + barFooStrOrUndef + 'str' - ~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] 'str' + barFooStrOrNull + 'str' - ~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + neverType + 'str' 'str' + String(barFooStrOrUndef) + 'str' 'str' + String(barFooStrOrNull) + 'str' + 'str' + String(neverType) + 'str' 'str' + barFooStrOrUndef.toString() + 'str' 'str' + barFooStrOrNull.toString() + 'str' + 'str' + neverType.toString() + 'str' diff --git a/test/rules/strict-string-expressions/allow-empty-types/tsconfig.json b/test/rules/strict-string-expressions/allow-empty-types/tsconfig.json new file mode 100644 index 00000000000..aa5798f3ce6 --- /dev/null +++ b/test/rules/strict-string-expressions/allow-empty-types/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { + } +} diff --git a/test/rules/strict-string-expressions/allow-empty-types/tslint.json b/test/rules/strict-string-expressions/allow-empty-types/tslint.json new file mode 100644 index 00000000000..2f34470b51f --- /dev/null +++ b/test/rules/strict-string-expressions/allow-empty-types/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "strict-string-expressions": [true, { "allow-empty-types": true }] + } +} diff --git a/test/rules/strict-string-expressions/test.ts.fix b/test/rules/strict-string-expressions/default/test.ts.fix similarity index 93% rename from test/rules/strict-string-expressions/test.ts.fix rename to test/rules/strict-string-expressions/default/test.ts.fix index 8c37b644dc6..51d89e18392 100644 --- a/test/rules/strict-string-expressions/test.ts.fix +++ b/test/rules/strict-string-expressions/default/test.ts.fix @@ -99,21 +99,28 @@ const barFooStrOrUndef: string | undefined; const barFooStrOrNull: string | null; + const neverType: never; `${String(barFooStrOrUndef)}` `${String(barFooStrOrNull)}` + `${String(neverType)}` `${String(barFooStrOrUndef)}` `${String(barFooStrOrNull)}` + `${String(neverType)}` `${barFooStrOrUndef.toString()}` `${barFooStrOrNull.toString()}` + `${neverType.toString()}` 'str' + String(barFooStrOrUndef) + 'str' 'str' + String(barFooStrOrNull) + 'str' + 'str' + String(neverType) + 'str' 'str' + String(barFooStrOrUndef) + 'str' 'str' + String(barFooStrOrNull) + 'str' + 'str' + String(neverType) + 'str' 'str' + barFooStrOrUndef.toString() + 'str' 'str' + barFooStrOrNull.toString() + 'str' + 'str' + neverType.toString() + 'str' diff --git a/test/rules/strict-string-expressions/default/test.ts.lint b/test/rules/strict-string-expressions/default/test.ts.lint new file mode 100644 index 00000000000..1749481955f --- /dev/null +++ b/test/rules/strict-string-expressions/default/test.ts.lint @@ -0,0 +1,144 @@ + const fooAny: any; + const fooStr: string = 'foo'; + const fooNumber = 2; + class FooClass {} + class ClassWithToString { + public static toString () { return ''; } + public toString () { return ''; } + } + const classWithToString = new ClassWithToString(); + const FooStr = new String('foo'); + const fooArr = ['foo']; + const emptyArr = []; + const stringUni = "foo" | "bar"; + + `foo` + `${'str literal'}` + `${123}` + `${fooAny}` + `${fooStr}` + `${stringUni}` + `${fooNumber}` + `${(typeof window)}` + `${FooClass}` + ~~~~~~~~~~ [Explicit conversion to string type required] + `${ClassWithToString}` + ~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${classWithToString}` + ~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${FooStr}` + ~~~~~~~~ [Explicit conversion to string type required] + `${fooArr}` + ~~~~~~~~ [Explicit conversion to string type required] + `${emptyArr}` + ~~~~~~~~~~ [Explicit conversion to string type required] + + `${String('str literal')}` + `${String(123)}` + `${String(fooAny)}` + `${String(fooStr)}` + `${String(stringUni)}` + `${String(fooNumber)}` + `${String((typeof window))}` + `${String(FooClass)}` + `${String(ClassWithToString)}` + `${String(classWithToString)}` + `${String(FooStr)}` + `${String(fooArr)}` + `${String(emptyArr)}` + + `${'str literal'.toString()}` + `${123..toString()}` + `${fooAny.toString()}` + `${fooStr.toString()}` + `${stringUni.toString()}` + `${fooNumber.toString()}` + `${(typeof window).toString()}` + `${FooClass.toString()}` + `${ClassWithToString.toString()}` + `${classWithToString.toString()}` + `${FooStr.toString()}` + `${fooArr.toString()}` + `${emptyArr.toString()}` + + 'str' + 'str literal' + 'str' + 'str' + 123 + 'str' + 'str' + fooAny + 'str' + 'str' + fooStr + 'str' + 'str' + stringUni + 'str' + 'str' + fooNumber + 'str' + 'str' + (typeof window) + 'str' + 'str' + FooClass + 'str' + ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + ClassWithToString + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + classWithToString + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + FooStr + 'str' + ~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + fooArr + 'str' + ~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + emptyArr + 'str' + ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + + 'str' + String('str literal') + 'str' + 'str' + String(123) + 'str' + 'str' + String(fooAny) + 'str' + 'str' + String(fooStr) + 'str' + 'str' + String(stringUni) + 'str' + 'str' + String(fooNumber) + 'str' + 'str' + String((typeof window)) + 'str' + 'str' + String(FooClass) + 'str' + 'str' + String(ClassWithToString) + 'str' + 'str' + String(classWithToString) + 'str' + 'str' + String(FooStr) + 'str' + 'str' + String(fooArr) + 'str' + 'str' + String(emptyArr) + 'str' + + 'str' + 'str literal'.toString() + 'str' + 'str' + 123..toString() + 'str' + 'str' + fooAny.toString() + 'str' + 'str' + fooStr.toString() + 'str' + 'str' + stringUni.toString() + 'str' + 'str' + fooNumber.toString() + 'str' + 'str' + (typeof window).toString() + 'str' + 'str' + FooClass.toString() + 'str' + 'str' + ClassWithToString.toString() + 'str' + 'str' + classWithToString.toString() + 'str' + 'str' + FooStr.toString() + 'str' + 'str' + fooArr.toString() + 'str' + 'str' + emptyArr.toString() + 'str' + + const barFooStrOrUndef: string | undefined; + const barFooStrOrNull: string | null; + const neverType: never; + + `${barFooStrOrUndef}` + ~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${barFooStrOrNull}` + ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + `${neverType}` + ~~~~~~~~~~~ [Explicit conversion to string type required] + + `${String(barFooStrOrUndef)}` + `${String(barFooStrOrNull)}` + `${String(neverType)}` + + `${barFooStrOrUndef.toString()}` + `${barFooStrOrNull.toString()}` + `${neverType.toString()}` + + 'str' + barFooStrOrUndef + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + barFooStrOrNull + 'str' + ~~~~~~~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + neverType + 'str' + ~~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + + 'str' + String(barFooStrOrUndef) + 'str' + 'str' + String(barFooStrOrNull) + 'str' + 'str' + String(neverType) + 'str' + + 'str' + barFooStrOrUndef.toString() + 'str' + 'str' + barFooStrOrNull.toString() + 'str' + 'str' + neverType.toString() + 'str' diff --git a/test/rules/strict-string-expressions/tsconfig.json b/test/rules/strict-string-expressions/default/tsconfig.json similarity index 100% rename from test/rules/strict-string-expressions/tsconfig.json rename to test/rules/strict-string-expressions/default/tsconfig.json diff --git a/test/rules/strict-string-expressions/tslint.json b/test/rules/strict-string-expressions/default/tslint.json similarity index 100% rename from test/rules/strict-string-expressions/tslint.json rename to test/rules/strict-string-expressions/default/tslint.json From bb841e051215181b328fa1711c6a461cfb604406 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 21:43:29 +0300 Subject: [PATCH 20/27] feat: allo empty types in all.ts config --- src/configs/all.ts | 2 +- .../allow-empty-types/tslint.json | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/configs/all.ts b/src/configs/all.ts index 2304996aeb1..25530f0f9a5 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -150,7 +150,7 @@ export const rules = { "restrict-plus-operands": true, "static-this": true, "strict-boolean-expressions": true, - "strict-string-expressions": true, + "strict-string-expressions": [true, { "allow-empty-types": true }], "strict-comparisons": true, "strict-type-predicates": true, "switch-default": true, diff --git a/test/rules/strict-string-expressions/allow-empty-types/tslint.json b/test/rules/strict-string-expressions/allow-empty-types/tslint.json index 2f34470b51f..270026fc7f4 100644 --- a/test/rules/strict-string-expressions/allow-empty-types/tslint.json +++ b/test/rules/strict-string-expressions/allow-empty-types/tslint.json @@ -1,5 +1,10 @@ { - "rules": { - "strict-string-expressions": [true, { "allow-empty-types": true }] - } + "rules": { + "strict-string-expressions": [ + true, + { + "allow-empty-types": true + } + ] + } } From 2c9603e16060b9a3e90adfb9d04512824137b930 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 21:51:04 +0300 Subject: [PATCH 21/27] style(strictStringExpressionsRule): fix lint rules --- src/rules/strictStringExpressionsRule.ts | 42 ++++++++++++++---------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 74bbfa06ae8..e6517c0de2f 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -20,7 +20,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; -const OPTION_ALLOW_EMPTY_TYPES = 'allow-empty-types'; +const OPTION_ALLOW_EMPTY_TYPES = "allow-empty-types"; interface Options { [OPTION_ALLOW_EMPTY_TYPES]?: boolean; @@ -39,17 +39,6 @@ export class Rule extends Lint.Rules.TypedRule { * String literals ("foo" + bar) * ES6 templates (\`foo \${bar}\`)`, hasFix: true, - optionsDescription: Lint.Utils.dedent` - Following arguments may be optionally provided: - * \`${OPTION_ALLOW_EMPTY_TYPES}\` allows \`null\`, \`undefined\` and \`never\` to be passed into strings without explicit conversion`, - options: { - type: "object", - properties: { - [OPTION_ALLOW_EMPTY_TYPES]: { - type: "boolean", - }, - }, - }, optionExamples: [ true, [ @@ -59,6 +48,17 @@ export class Rule extends Lint.Rules.TypedRule { }, ], ], + options: { + properties: { + [OPTION_ALLOW_EMPTY_TYPES]: { + type: "boolean", + }, + }, + type: "object", + }, + optionsDescription: Lint.Utils.dedent` + Following arguments may be optionally provided: + * \`${OPTION_ALLOW_EMPTY_TYPES}\` allows \`null\`, \`undefined\` and \`never\` to be passed into strings without explicit conversion`, requiresTypeInfo: true, ruleName: "strict-string-expressions", type: "functionality", @@ -66,7 +66,12 @@ export class Rule extends Lint.Rules.TypedRule { }; public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk, this.getRuleOptions(), program.getTypeChecker()); + return this.applyWithFunction( + sourceFile, + walk, + this.getRuleOptions(), + program.getTypeChecker(), + ); } private getRuleOptions(): Options { @@ -87,11 +92,11 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { const leftIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.left), - options + options, ); const rightIsPassedAsIs = isTypeConvertsToStringEasily( checker.getTypeAtLocation(binaryExpr.right), - options + options, ); const leftIsFailed = !leftIsPassedAsIs && rightIsPassedAsIs; const rightIsFailed = leftIsPassedAsIs && !rightIsPassedAsIs; @@ -125,14 +130,17 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } } -const isEmptyType = (type: ts.Type): boolean => isTypeFlagSet(type, ts.TypeFlags.Null) || +const isEmptyType = (type: ts.Type): boolean => + isTypeFlagSet(type, ts.TypeFlags.Null) || isTypeFlagSet(type, ts.TypeFlags.VoidLike) || isTypeFlagSet(type, ts.TypeFlags.Undefined) || isTypeFlagSet(type, ts.TypeFlags.Never); function isTypeConvertsToStringEasily(type: ts.Type, options: Options): boolean { if (isUnionType(type)) { - return type.types.every((unionAtomicType) => isTypeConvertsToStringEasily(unionAtomicType, options)); + return type.types.every(unionAtomicType => + isTypeConvertsToStringEasily(unionAtomicType, options), + ); } if (options[OPTION_ALLOW_EMPTY_TYPES] && isEmptyType(type)) { From cf3f90d920ff16a0bbf525494ce2842c9b676887 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:00:43 +0300 Subject: [PATCH 22/27] feat(strictStringExpressionsRule): allow booleans --- src/rules/strictStringExpressionsRule.ts | 1 + .../allow-empty-types/test.ts.fix | 7 +++++++ .../allow-empty-types/test.ts.lint | 7 +++++++ test/rules/strict-string-expressions/default/test.ts.fix | 7 +++++++ test/rules/strict-string-expressions/default/test.ts.lint | 7 +++++++ 5 files changed, 29 insertions(+) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index e6517c0de2f..918ac8603ae 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -148,6 +148,7 @@ function isTypeConvertsToStringEasily(type: ts.Type, options: Options): boolean } return ( + isTypeFlagSet(type, ts.TypeFlags.BooleanLike) || isTypeFlagSet(type, ts.TypeFlags.StringOrNumberLiteral) || isTypeFlagSet(type, ts.TypeFlags.NumberLike) || isTypeFlagSet(type, ts.TypeFlags.StringLike) || diff --git a/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix b/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix index 2e383f7bef3..ed0c9eec177 100644 --- a/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix +++ b/test/rules/strict-string-expressions/allow-empty-types/test.ts.fix @@ -11,10 +11,12 @@ const fooArr = ['foo']; const emptyArr = []; const stringUni = "foo" | "bar"; + const booleanVar: boolean; `foo` `${'str literal'}` `${123}` + `${booleanVar}` `${fooAny}` `${fooStr}` `${stringUni}` @@ -29,6 +31,7 @@ `${String('str literal')}` `${String(123)}` + `${String(booleanVar)}` `${String(fooAny)}` `${String(fooStr)}` `${String(stringUni)}` @@ -43,6 +46,7 @@ `${'str literal'.toString()}` `${123..toString()}` + `${booleanVar.toString()}` `${fooAny.toString()}` `${fooStr.toString()}` `${stringUni.toString()}` @@ -57,6 +61,7 @@ 'str' + 'str literal' + 'str' 'str' + 123 + 'str' + 'str' + booleanVar + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + stringUni + 'str' @@ -71,6 +76,7 @@ 'str' + String('str literal') + 'str' 'str' + String(123) + 'str' + 'str' + String(booleanVar) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + String(stringUni) + 'str' @@ -85,6 +91,7 @@ 'str' + 'str literal'.toString() + 'str' 'str' + 123..toString() + 'str' + 'str' + booleanVar.toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + stringUni.toString() + 'str' diff --git a/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint b/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint index 9008e793079..0660ca94379 100644 --- a/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint +++ b/test/rules/strict-string-expressions/allow-empty-types/test.ts.lint @@ -11,10 +11,12 @@ const fooArr = ['foo']; const emptyArr = []; const stringUni = "foo" | "bar"; + const booleanVar: boolean; `foo` `${'str literal'}` `${123}` + `${booleanVar}` `${fooAny}` `${fooStr}` `${stringUni}` @@ -35,6 +37,7 @@ `${String('str literal')}` `${String(123)}` + `${String(booleanVar)}` `${String(fooAny)}` `${String(fooStr)}` `${String(stringUni)}` @@ -49,6 +52,7 @@ `${'str literal'.toString()}` `${123..toString()}` + `${booleanVar.toString()}` `${fooAny.toString()}` `${fooStr.toString()}` `${stringUni.toString()}` @@ -63,6 +67,7 @@ 'str' + 'str literal' + 'str' 'str' + 123 + 'str' + 'str' + booleanVar + 'str' 'str' + fooAny + 'str' 'str' + fooStr + 'str' 'str' + stringUni + 'str' @@ -83,6 +88,7 @@ 'str' + String('str literal') + 'str' 'str' + String(123) + 'str' + 'str' + String(booleanVar) + 'str' 'str' + String(fooAny) + 'str' 'str' + String(fooStr) + 'str' 'str' + String(stringUni) + 'str' @@ -97,6 +103,7 @@ 'str' + 'str literal'.toString() + 'str' 'str' + 123..toString() + 'str' + 'str' + booleanVar.toString() + 'str' 'str' + fooAny.toString() + 'str' 'str' + fooStr.toString() + 'str' 'str' + stringUni.toString() + 'str' diff --git a/test/rules/strict-string-expressions/default/test.ts.fix b/test/rules/strict-string-expressions/default/test.ts.fix index 51d89e18392..dd4b6ee8d2c 100644 --- a/test/rules/strict-string-expressions/default/test.ts.fix +++ b/test/rules/strict-string-expressions/default/test.ts.fix @@ -11,6 +11,7 @@ const fooArr = ['foo']; const emptyArr = []; const stringUni = "foo" | "bar"; + const booleanVar: boolean; `foo` `${'str literal'}` @@ -26,6 +27,7 @@ `${String(FooStr)}` `${String(fooArr)}` `${String(emptyArr)}` + `${booleanVar}` `${String('str literal')}` `${String(123)}` @@ -40,6 +42,7 @@ `${String(FooStr)}` `${String(fooArr)}` `${String(emptyArr)}` + `${String(booleanVar)}` `${'str literal'.toString()}` `${123..toString()}` @@ -54,6 +57,7 @@ `${FooStr.toString()}` `${fooArr.toString()}` `${emptyArr.toString()}` + `${booleanVar.toString()}` 'str' + 'str literal' + 'str' 'str' + 123 + 'str' @@ -68,6 +72,7 @@ 'str' + String(FooStr) + 'str' 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + booleanVar + 'str' 'str' + String('str literal') + 'str' 'str' + String(123) + 'str' @@ -82,6 +87,7 @@ 'str' + String(FooStr) + 'str' 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + String(booleanVar) + 'str' 'str' + 'str literal'.toString() + 'str' 'str' + 123..toString() + 'str' @@ -96,6 +102,7 @@ 'str' + FooStr.toString() + 'str' 'str' + fooArr.toString() + 'str' 'str' + emptyArr.toString() + 'str' + 'str' + booleanVar.toString() + 'str' const barFooStrOrUndef: string | undefined; const barFooStrOrNull: string | null; diff --git a/test/rules/strict-string-expressions/default/test.ts.lint b/test/rules/strict-string-expressions/default/test.ts.lint index 1749481955f..b7f29b9ed0c 100644 --- a/test/rules/strict-string-expressions/default/test.ts.lint +++ b/test/rules/strict-string-expressions/default/test.ts.lint @@ -11,6 +11,7 @@ const fooArr = ['foo']; const emptyArr = []; const stringUni = "foo" | "bar"; + const booleanVar: boolean; `foo` `${'str literal'}` @@ -32,6 +33,7 @@ ~~~~~~~~ [Explicit conversion to string type required] `${emptyArr}` ~~~~~~~~~~ [Explicit conversion to string type required] + `${booleanVar}` `${String('str literal')}` `${String(123)}` @@ -46,6 +48,7 @@ `${String(FooStr)}` `${String(fooArr)}` `${String(emptyArr)}` + `${String(booleanVar)}` `${'str literal'.toString()}` `${123..toString()}` @@ -60,6 +63,7 @@ `${FooStr.toString()}` `${fooArr.toString()}` `${emptyArr.toString()}` + `${booleanVar.toString()}` 'str' + 'str literal' + 'str' 'str' + 123 + 'str' @@ -80,6 +84,7 @@ ~~~~~~~~~~~~~~ [Explicit conversion to string type required] 'str' + emptyArr + 'str' ~~~~~~~~~~~~~~~~ [Explicit conversion to string type required] + 'str' + booleanVar + 'str' 'str' + String('str literal') + 'str' 'str' + String(123) + 'str' @@ -94,6 +99,7 @@ 'str' + String(FooStr) + 'str' 'str' + String(fooArr) + 'str' 'str' + String(emptyArr) + 'str' + 'str' + String(booleanVar) + 'str' 'str' + 'str literal'.toString() + 'str' 'str' + 123..toString() + 'str' @@ -108,6 +114,7 @@ 'str' + FooStr.toString() + 'str' 'str' + fooArr.toString() + 'str' 'str' + emptyArr.toString() + 'str' + 'str' + booleanVar.toString() + 'str' const barFooStrOrUndef: string | undefined; const barFooStrOrNull: string | null; From 6ee97af343a2ca7f05ee4bce4948ac3b63682772 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:01:09 +0300 Subject: [PATCH 23/27] style(linter): fix codestyle --- src/linter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linter.ts b/src/linter.ts index 47edb42312a..5a86ae92772 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -187,7 +187,7 @@ export class Linter { this.options.formatter !== undefined ? this.options.formatter : "prose"; const Formatter = findFormatter(formatterName, this.options.formattersDirectory); if (Formatter === undefined) { - throw new Error(`formatter '${formatterName}' not found`); + throw new Error(`formatter '${String(formatterName)}' not found`); } const formatter = new Formatter(); From c460673ac7d39813b77b4a3148386fb8c954a575 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:10:49 +0300 Subject: [PATCH 24/27] refactor(strictStringExpressionsRule): rename private helpers --- src/rules/strictStringExpressionsRule.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index 918ac8603ae..e20ee03c7a1 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -90,11 +90,11 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.BinaryExpression: { const binaryExpr = node as ts.BinaryExpression; if (binaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - const leftIsPassedAsIs = isTypeConvertsToStringEasily( + const leftIsPassedAsIs = typeCanBeStringifiedEasily( checker.getTypeAtLocation(binaryExpr.left), options, ); - const rightIsPassedAsIs = isTypeConvertsToStringEasily( + const rightIsPassedAsIs = typeCanBeStringifiedEasily( checker.getTypeAtLocation(binaryExpr.right), options, ); @@ -110,7 +110,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { case ts.SyntaxKind.TemplateSpan: { const templateSpanNode = node as ts.TemplateSpan; const type = checker.getTypeAtLocation(templateSpanNode.expression); - const shouldPassAsIs = isTypeConvertsToStringEasily(type, options); + const shouldPassAsIs = typeCanBeStringifiedEasily(type, options); if (!shouldPassAsIs) { const { expression } = templateSpanNode; addFailure(templateSpanNode, expression); @@ -130,20 +130,20 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker): void { } } -const isEmptyType = (type: ts.Type): boolean => +const typeIsEmpty = (type: ts.Type): boolean => isTypeFlagSet(type, ts.TypeFlags.Null) || isTypeFlagSet(type, ts.TypeFlags.VoidLike) || isTypeFlagSet(type, ts.TypeFlags.Undefined) || isTypeFlagSet(type, ts.TypeFlags.Never); -function isTypeConvertsToStringEasily(type: ts.Type, options: Options): boolean { +function typeCanBeStringifiedEasily(type: ts.Type, options: Options): boolean { if (isUnionType(type)) { return type.types.every(unionAtomicType => - isTypeConvertsToStringEasily(unionAtomicType, options), + typeCanBeStringifiedEasily(unionAtomicType, options), ); } - if (options[OPTION_ALLOW_EMPTY_TYPES] && isEmptyType(type)) { + if (options[OPTION_ALLOW_EMPTY_TYPES] && typeIsEmpty(type)) { return true; } From b99df8d99aec25e2dccfa114bfb900c71dc72800 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:14:31 +0300 Subject: [PATCH 25/27] docs(strictStringExpressionsRule): update license year --- src/rules/strictStringExpressionsRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index e20ee03c7a1..f0d45383b49 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Palantir Technologies, Inc. + * Copyright 2019 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 2ac3f27b0deb30b162b5a2e444f2a60f4bffb2d8 Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:19:07 +0300 Subject: [PATCH 26/27] refactor(strictStringExpressionsRule): make allow-empty-types to appear default option --- src/configs/all.ts | 2 +- src/rules/strictStringExpressionsRule.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/configs/all.ts b/src/configs/all.ts index 25530f0f9a5..2304996aeb1 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -150,7 +150,7 @@ export const rules = { "restrict-plus-operands": true, "static-this": true, "strict-boolean-expressions": true, - "strict-string-expressions": [true, { "allow-empty-types": true }], + "strict-string-expressions": true, "strict-comparisons": true, "strict-type-predicates": true, "switch-default": true, diff --git a/src/rules/strictStringExpressionsRule.ts b/src/rules/strictStringExpressionsRule.ts index f0d45383b49..e614a77f9ae 100644 --- a/src/rules/strictStringExpressionsRule.ts +++ b/src/rules/strictStringExpressionsRule.ts @@ -76,7 +76,9 @@ export class Rule extends Lint.Rules.TypedRule { private getRuleOptions(): Options { if (this.ruleArguments[0] === undefined) { - return {}; + return { + [OPTION_ALLOW_EMPTY_TYPES]: true, + }; } else { return this.ruleArguments[0] as Options; } From bb95c452dd9bd550f1e9d35a8200dd4d912fd15f Mon Sep 17 00:00:00 2001 From: Max Sysoev Date: Sun, 28 Jul 2019 22:23:32 +0300 Subject: [PATCH 27/27] test(stringStrictExpressions): correct test --- .../strict-string-expressions/default/tslint.json | 5 ----- .../{default => disallow-empty-types}/test.ts.fix | 0 .../{default => disallow-empty-types}/test.ts.lint | 0 .../{default => disallow-empty-types}/tsconfig.json | 0 .../disallow-empty-types/tslint.json | 10 ++++++++++ 5 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 test/rules/strict-string-expressions/default/tslint.json rename test/rules/strict-string-expressions/{default => disallow-empty-types}/test.ts.fix (100%) rename test/rules/strict-string-expressions/{default => disallow-empty-types}/test.ts.lint (100%) rename test/rules/strict-string-expressions/{default => disallow-empty-types}/tsconfig.json (100%) create mode 100644 test/rules/strict-string-expressions/disallow-empty-types/tslint.json diff --git a/test/rules/strict-string-expressions/default/tslint.json b/test/rules/strict-string-expressions/default/tslint.json deleted file mode 100644 index dcd6ed572f2..00000000000 --- a/test/rules/strict-string-expressions/default/tslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "strict-string-expressions": true - } -} diff --git a/test/rules/strict-string-expressions/default/test.ts.fix b/test/rules/strict-string-expressions/disallow-empty-types/test.ts.fix similarity index 100% rename from test/rules/strict-string-expressions/default/test.ts.fix rename to test/rules/strict-string-expressions/disallow-empty-types/test.ts.fix diff --git a/test/rules/strict-string-expressions/default/test.ts.lint b/test/rules/strict-string-expressions/disallow-empty-types/test.ts.lint similarity index 100% rename from test/rules/strict-string-expressions/default/test.ts.lint rename to test/rules/strict-string-expressions/disallow-empty-types/test.ts.lint diff --git a/test/rules/strict-string-expressions/default/tsconfig.json b/test/rules/strict-string-expressions/disallow-empty-types/tsconfig.json similarity index 100% rename from test/rules/strict-string-expressions/default/tsconfig.json rename to test/rules/strict-string-expressions/disallow-empty-types/tsconfig.json diff --git a/test/rules/strict-string-expressions/disallow-empty-types/tslint.json b/test/rules/strict-string-expressions/disallow-empty-types/tslint.json new file mode 100644 index 00000000000..265d57b2aaf --- /dev/null +++ b/test/rules/strict-string-expressions/disallow-empty-types/tslint.json @@ -0,0 +1,10 @@ +{ + "rules": { + "strict-string-expressions": [ + true, + { + "allow-empty-types": false + } + ] + } +}