Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): [no-useless-template-literals] add new rule (#7957)
* feat(eslint-plugin): [no-useless-template-literals] add new rule Closes #2846 * fix tests * thank you josh * hopefully fix tests? * support template literals with new lines * support also quotes * fix all files (damn, we need an auto fixer...) * wip * report on specific node * fix docs * fix lint * wip * fix lint * revert unrelated changes * more reverts * wip * wip --------- Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
- Loading branch information
1 parent
c9661c8
commit ff75785
Showing
12 changed files
with
505 additions
and
6 deletions.
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
packages/eslint-plugin/docs/rules/no-useless-template-literals.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
--- | ||
description: 'Disallow unnecessary template literals.' | ||
--- | ||
|
||
> 🛑 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 can be simplified to a normal string literal. | ||
|
||
## Examples | ||
|
||
<!--tabs--> | ||
|
||
### ❌ Incorrect | ||
|
||
```ts | ||
const ab1 = `${'a'}${'b'}`; | ||
const ab2 = `a${'b'}`; | ||
|
||
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}`; | ||
``` | ||
|
||
### ✅ Correct | ||
|
||
```ts | ||
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; | ||
``` | ||
|
||
<!--/tabs--> | ||
|
||
## When Not To Use It | ||
|
||
When you want to allow string expressions inside template literals. | ||
|
||
## Related To | ||
|
||
- [`restrict-template-expressions`](./restrict-template-expressions.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
packages/eslint-plugin/src/rules/no-useless-template-literals.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import type { TSESTree } from '@typescript-eslint/utils'; | ||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'; | ||
import * as ts from 'typescript'; | ||
|
||
import { | ||
createRule, | ||
getConstrainedTypeAtLocation, | ||
getParserServices, | ||
isTypeFlagSet, | ||
isUndefinedIdentifier, | ||
} from '../util'; | ||
|
||
type MessageId = 'noUselessTemplateLiteral'; | ||
|
||
export default createRule<[], MessageId>({ | ||
name: 'no-useless-template-literals', | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Disallow unnecessary template literals', | ||
recommended: 'strict', | ||
requiresTypeChecking: true, | ||
}, | ||
messages: { | ||
noUselessTemplateLiteral: | ||
'Template literal expression is unnecessary and can be simplified.', | ||
}, | ||
schema: [], | ||
}, | ||
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); | ||
} | ||
|
||
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: 'noUselessTemplateLiteral', | ||
}); | ||
|
||
return; | ||
} | ||
|
||
const literalsOrUndefinedExpressions = node.expressions.filter( | ||
(expression): expression is TSESTree.Literal | TSESTree.Identifier => | ||
expression.type === AST_NODE_TYPES.Literal || | ||
isUndefinedIdentifier(expression), | ||
); | ||
|
||
literalsOrUndefinedExpressions.forEach(expression => { | ||
context.report({ | ||
node: expression, | ||
messageId: 'noUselessTemplateLiteral', | ||
}); | ||
}); | ||
}, | ||
}; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.