diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx index 9db56432d931..1fe39d412dd1 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx @@ -14,8 +14,6 @@ This rule reports template literals that contain substitution expressions (also :::info[Migration from `no-useless-template-literals`] This rule was formerly known as [`no-useless-template-literals`](./no-useless-template-literals.mdx). -We encourage users to migrate to the new name, `no-unnecessary-template-expression`, as the old name will be removed in a future major version of typescript-eslint. - The new name is a drop-in replacement with identical functionality. ::: diff --git a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx index 20986f052ff9..812db678b319 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx +++ b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx @@ -1,23 +1,5 @@ ---- -description: 'Disallow unnecessary template literals.' ---- +:::danger Deprecated -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +This rule has been renamed to [`no-unnecessary-template-expression`](./no-unnecessary-template-expression.mdx). See [#8544](https://github.com/typescript-eslint/typescript-eslint/issues/8544) for more information. -> 🛑 This file is source code, not the primary documentation location! 🛑 -> -> See **https://typescript-eslint.io/rules/no-useless-template-literals** for documentation. - -This rule reports template literals that contain substitution expressions (also variously referred to as embedded expressions or string interpolations) that are unnecessary and can be simplified. - -:::warning -This rule is being renamed to [`no-unnecessary-template-expression`](./no-unnecessary-template-expression.mdx). -The current name, `no-useless-template-literals`, will be removed in a future major version of typescript-eslint. - -After the creation of this rule, it was realized that the name `no-useless-template-literals` could be misleading, seeing as this rule only targets template literals with substitution expressions. -In particular, it does _not_ aim to flag useless template literals that look like `` `this` `` and could be simplified to `"this"`. -If you are looking for such a rule, you can configure the [`@stylistic/ts/quotes`](https://eslint.style/rules/ts/quotes) rule to do this. ::: - -{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 53b644c4154e..9a45d83452cf 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -39,7 +39,6 @@ export = { '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', - '@typescript-eslint/no-useless-template-literals': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', '@typescript-eslint/prefer-destructuring': 'off', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index e626994f8644..c167013211c0 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -85,7 +85,6 @@ import noUnusedVars from './no-unused-vars'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; -import noUselessTemplateLiterals from './no-useless-template-literals'; import noVarRequires from './no-var-requires'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; import onlyThrowError from './only-throw-error'; @@ -210,7 +209,6 @@ export default { 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, - 'no-useless-template-literals': noUselessTemplateLiterals, 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, 'only-throw-error': onlyThrowError, diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts deleted file mode 100644 index 01531c883130..000000000000 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getConstrainedTypeAtLocation, - getParserServices, - getStaticStringValue, - isTypeFlagSet, - isUndefinedIdentifier, -} from '../util'; - -type MessageId = 'noUnnecessaryTemplateExpression'; - -export default createRule<[], MessageId>({ - name: 'no-useless-template-literals', - meta: { - fixable: 'code', - type: 'suggestion', - docs: { - description: 'Disallow unnecessary template literals', - requiresTypeChecking: true, - }, - messages: { - noUnnecessaryTemplateExpression: - 'Template literal expression is unnecessary and can be simplified.', - }, - schema: [], - deprecated: true, - replacedBy: ['@typescript-eslint/no-unnecessary-template-expression'], - }, - defaultOptions: [], - create(context) { - const services = getParserServices(context); - - function isUnderlyingTypeString( - expression: TSESTree.Expression, - ): expression is TSESTree.StringLiteral | TSESTree.Identifier { - const type = getConstrainedTypeAtLocation(services, expression); - - const isString = (t: ts.Type): boolean => { - return isTypeFlagSet(t, ts.TypeFlags.StringLike); - }; - - if (type.isUnion()) { - return type.types.every(isString); - } - - if (type.isIntersection()) { - return type.types.some(isString); - } - - return isString(type); - } - - function isLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.Literal; - } - - function isTemplateLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.TemplateLiteral; - } - - function isInfinityIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'Infinity' - ); - } - - function isNaNIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'NaN' - ); - } - - return { - TemplateLiteral(node: TSESTree.TemplateLiteral): void { - if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) { - return; - } - - const hasSingleStringVariable = - node.quasis.length === 2 && - node.quasis[0].value.raw === '' && - node.quasis[1].value.raw === '' && - node.expressions.length === 1 && - isUnderlyingTypeString(node.expressions[0]); - - if (hasSingleStringVariable) { - context.report({ - node: node.expressions[0], - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const [prevQuasi, nextQuasi] = node.quasis; - - // Remove the quasis and backticks. - return [ - fixer.removeRange([ - prevQuasi.range[1] - 3, - node.expressions[0].range[0], - ]), - - fixer.removeRange([ - node.expressions[0].range[1], - nextQuasi.range[0] + 2, - ]), - ]; - }, - }); - - return; - } - - const fixableExpressions = node.expressions.filter( - expression => - isLiteral(expression) || - isTemplateLiteral(expression) || - isUndefinedIdentifier(expression) || - isInfinityIdentifier(expression) || - isNaNIdentifier(expression), - ); - - fixableExpressions.forEach(expression => { - context.report({ - node: expression, - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const index = node.expressions.indexOf(expression); - const prevQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - - // Remove the quasis' parts that are related to the current expression. - const fixes = [ - fixer.removeRange([ - prevQuasi.range[1] - 2, - expression.range[0], - ]), - - fixer.removeRange([ - expression.range[1], - nextQuasi.range[0] + 1, - ]), - ]; - - const stringValue = getStaticStringValue(expression); - - if (stringValue != null) { - const escapedValue = stringValue.replace(/([`$\\])/g, '\\$1'); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } else if (isTemplateLiteral(expression)) { - // Note that some template literals get handled in the previous branch too. - // Remove the beginning and trailing backtick characters. - fixes.push( - fixer.removeRange([ - expression.range[0], - expression.range[0] + 1, - ]), - fixer.removeRange([ - expression.range[1] - 1, - expression.range[1], - ]), - ); - } - - return fixes; - }, - }); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot deleted file mode 100644 index 3cc841703a98..000000000000 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-literals.shot +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Validating rule docs no-useless-template-literals.mdx code examples ESLint output 1`] = ` -"Incorrect - -const ab1 = \`\${'a'}\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. - ~~~ Template literal expression is unnecessary and can be simplified. -const ab2 = \`a\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. - -const stringWithNumber = \`\${'1 + 1 = '}\${2}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~ Template literal expression is unnecessary and can be simplified. - -const stringWithBoolean = \`\${'true is '}\${true}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~~~~ Template literal expression is unnecessary and can be simplified. - -const text = 'a'; -const wrappedText = \`\${text}\`; - ~~~~ Template literal expression is unnecessary and can be simplified. - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = \`\${intersectionWithString}\`; - ~~~~~~~~~~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. -" -`; - -exports[`Validating rule docs no-useless-template-literals.mdx code examples ESLint output 2`] = ` -"Correct - -const ab1 = 'ab'; -const ab2 = 'ab'; - -const stringWithNumber = \`1 + 1 = 2\`; - -const stringWithBoolean = \`true is true\`; - -const text = 'a'; -const wrappedText = text; - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = intersectionWithString; -" -`; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index e71524a0beac..53d0838af93e 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -157,6 +157,7 @@ describe('Validating rule docs', () => { // comments in the files for more information. 'no-duplicate-imports.mdx', 'no-parameter-properties.mdx', + 'no-useless-template-literals.mdx', ...oldStylisticRules, ]); diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts deleted file mode 100644 index c3900e2fe718..000000000000 --- a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts +++ /dev/null @@ -1,646 +0,0 @@ -import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; - -import rule from '../../src/rules/no-useless-template-literals'; -import { getFixturesRootDir } from '../RuleTester'; - -const rootPath = getFixturesRootDir(); - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: rootPath, - project: './tsconfig.json', - }, -}); - -ruleTester.run('no-useless-template-literals', rule, { - valid: [ - "const string = 'a';", - 'const string = `a`;', - ` - declare const string: 'a'; - \`\${string}b\`; - `, - - ` - declare const number: 1; - \`\${number}b\`; - `, - - ` - declare const boolean: true; - \`\${boolean}b\`; - `, - - ` - declare const nullish: null; - \`\${nullish}-undefined\`; - `, - - ` - declare const undefinedish: undefined; - \`\${undefinedish}\`; - `, - - ` - declare const left: 'a'; - declare const right: 'b'; - \`\${left}\${right}\`; - `, - - ` - declare const left: 'a'; - declare const right: 'c'; - \`\${left}b\${right}\`; - `, - - ` - declare const left: 'a'; - declare const center: 'b'; - declare const right: 'c'; - \`\${left}\${center}\${right}\`; - `, - - '`1 + 1 = ${1 + 1}`;', - - '`true && false = ${true && false}`;', - - "tag`${'a'}${'b'}`;", - - '`${function () {}}`;', - - '`${() => {}}`;', - - '`${(...args: any[]) => args}`;', - - ` - declare const number: 1; - \`\${number}\`; - `, - - ` - declare const boolean: true; - \`\${boolean}\`; - `, - - ` - declare const nullish: null; - \`\${nullish}\`; - `, - - ` - declare const union: string | number; - \`\${union}\`; - `, - - ` - declare const unknown: unknown; - \`\${unknown}\`; - `, - - ` - declare const never: never; - \`\${never}\`; - `, - - ` - declare const any: any; - \`\${any}\`; - `, - - ` - function func(arg: T) { - \`\${arg}\`; - } - `, - - ` - \`with - - new line\`; - `, - - ` - declare const a: 'a'; - - \`\${a} with - - new line\`; - `, - - noFormat` - \`with windows \r new line\`; - `, - - ` -\`not a useless \${String.raw\`nested interpolation \${a}\`}\`; - `, - ], - - invalid: [ - { - code: '`${1}`;', - output: '`1`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 5, - }, - ], - }, - { - code: '`${1n}`;', - output: '`1`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 6, - }, - ], - }, - { - code: '`${/a/}`;', - output: '`/a/`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 7, - }, - ], - }, - - { - code: noFormat`\`\${ 1 }\`;`, - output: '`1`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: noFormat`\`\${ 'a' }\`;`, - output: `'a';`, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: noFormat`\`\${ "a" }\`;`, - output: `"a";`, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: noFormat`\`\${ 'a' + 'b' }\`;`, - output: `'a' + 'b';`, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: '`${true}`;', - output: '`true`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 8, - }, - ], - }, - - { - code: noFormat`\`\${ true }\`;`, - output: '`true`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: '`${null}`;', - output: '`null`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 8, - }, - ], - }, - - { - code: noFormat`\`\${ null }\`;`, - output: '`null`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: '`${undefined}`;', - output: '`undefined`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 13, - }, - ], - }, - - { - code: noFormat`\`\${ undefined }\`;`, - output: '`undefined`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: '`${Infinity}`;', - output: '`Infinity`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 12, - }, - ], - }, - - { - code: '`${NaN}`;', - output: '`NaN`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 7, - }, - ], - }, - - { - code: "`${'a'} ${'b'}`;", - output: '`a b`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 7, - }, - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 11, - endColumn: 14, - }, - ], - }, - - { - code: noFormat`\`\${ 'a' } \${ 'b' }\`;`, - output: '`a b`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: ` - declare const b: 'b'; - \`a\${b}\${'c'}\`; - `, - output: ` - declare const b: 'b'; - \`a\${b}c\`; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 17, - endColumn: 20, - }, - ], - }, - - { - code: "`use${'less'}`;", - output: '`useless`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - }, - ], - }, - - { - code: '`use${`less`}`;', - output: '`useless`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - }, - ], - }, - - { - code: ` -declare const nested: string, interpolation: string; -\`use\${\`less\${nested}\${interpolation}\`}\`; - `, - output: ` -declare const nested: string, interpolation: string; -\`useless\${nested}\${interpolation}\`; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: noFormat` -\`u\${ - // hopefully this comment is not needed. - 'se' - -}\${ - \`le\${ \`ss\` }\` -}\`; - `, - output: [ - ` -\`use\${ - \`less\` -}\`; - `, - ` -\`useless\`; - `, - ], - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 4, - }, - { - messageId: 'noUnnecessaryTemplateExpression', - line: 7, - column: 3, - endLine: 7, - }, - { - messageId: 'noUnnecessaryTemplateExpression', - line: 7, - column: 10, - endLine: 7, - }, - ], - }, - { - code: noFormat` -\`use\${ - \`less\` -}\`; - `, - output: ` -\`useless\`; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 3, - endColumn: 9, - }, - ], - }, - - { - code: "`${'1 + 1 ='} ${2}`;", - output: '`1 + 1 = 2`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 13, - }, - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 17, - endColumn: 18, - }, - ], - }, - - { - code: "`${'a'} ${true}`;", - output: '`a true`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 7, - }, - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 11, - endColumn: 15, - }, - ], - }, - - { - code: ` - declare const string: 'a'; - \`\${string}\`; - `, - output: ` - declare const string: 'a'; - string; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 12, - endColumn: 18, - }, - ], - }, - - { - code: noFormat` - declare const string: 'a'; - \`\${ string }\`; - `, - output: ` - declare const string: 'a'; - string; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: "`${String(Symbol.for('test'))}`;", - output: "String(Symbol.for('test'));", - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 1, - column: 4, - endColumn: 30, - }, - ], - }, - - { - code: ` - declare const intersection: string & { _brand: 'test-brand' }; - \`\${intersection}\`; - `, - output: ` - declare const intersection: string & { _brand: 'test-brand' }; - intersection; - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 12, - endColumn: 24, - }, - ], - }, - - { - code: ` - function func(arg: T) { - \`\${arg}\`; - } - `, - output: ` - function func(arg: T) { - arg; - } - `, - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 14, - endColumn: 17, - }, - ], - }, - - { - code: "`${'`'}`;", - output: "'`';", - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: "`back${'`'}tick`;", - output: '`back\\`tick`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: "`dollar${'${`this is test`}'}sign`;", - output: '`dollar\\${\\`this is test\\`}sign`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: '`complex${\'`${"`${test}`"}`\'}case`;', - output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: "`some ${'\\\\${test}'} string`;", - output: '`some \\\\\\${test} string`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - - { - code: "`some ${'\\\\`'} string`;", - output: '`some \\\\\\` string`;', - errors: [ - { - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-literals.shot b/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-literals.shot deleted file mode 100644 index 785d465a8408..000000000000 --- a/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-literals.shot +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Rule schemas should be convertible to TS types for documentation purposes no-useless-template-literals 1`] = ` -" -# SCHEMA: - -[] - - -# TYPES: - -/** No options declared */ -type Options = [];" -`; diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index 703443e40be7..9df504415e37 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -42,7 +42,6 @@ export default ( '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', - '@typescript-eslint/no-useless-template-literals': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', '@typescript-eslint/prefer-destructuring': 'off', pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy