Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule S4624: Template literals should not be nested #233

Merged
merged 3 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
* Two branches in a conditional structure should not have exactly the same implementation ([`no-duplicated-branches`])
* Functions should not have identical implementations ([`no-identical-functions`])
* Boolean checks should not be inverted ([`no-inverted-boolean-check`]) (:wrench: *fixable*)
* Template literals should not be nested ([`no-nested-template-literals`])
* Boolean literals should not be redundant ([`no-redundant-boolean`])
* Jump statements should not be redundant ([`no-redundant-jump`])
* Conditionals should start on new lines ([`no-same-line-conditional`])
Expand All @@ -55,6 +56,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
[`no-identical-expressions`]: ./docs/rules/no-identical-expressions.md
[`no-identical-functions`]: ./docs/rules/no-identical-functions.md
[`no-inverted-boolean-check`]: ./docs/rules/no-inverted-boolean-check.md
[`no-nested-template-literals`]: ./docs/rules/no-nested-template-literals.md
[`no-one-iteration-loop`]: ./docs/rules/no-one-iteration-loop.md
[`no-redundant-boolean`]: ./docs/rules/no-redundant-boolean.md
[`no-redundant-jump`]: ./docs/rules/no-redundant-jump.md
Expand Down
24 changes: 24 additions & 0 deletions docs/rules/no-nested-template-literals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# no-nested-template-literals

Template literals (previously named "template strings") are an elegant way to build a string without using the `+` operator to make strings concatenation more readable.

However, it’s possible to build complex string literals by nesting together multiple template literals, and therefore lose readability and maintainability.

In such situations, it’s preferable to move the nested template into a separate statement.

## Noncompliant Code Example

```javascript
let color = "red";
let count = 3;
let message = `I have ${color ? `${count} ${color}` : count} apples`; // Noncompliant; nested template strings not easy to read
```

## Compliant Solution

```javascript
let color = "red";
let count = 3;
let apples = color ? `${count} ${color}` : count;
let message = `I have ${apples} apples`;
```
82 changes: 82 additions & 0 deletions ruling/snapshots/no-nested-template-literals
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
src/create-react-app/packages/create-react-app/createReactApp.js: 141,380,550
src/create-react-app/packages/react-dev-utils/WebpackDevServerUtils.js: 111,407
src/create-react-app/packages/react-error-overlay/src/utils/stack-frame.js: 115,115
src/create-react-app/packages/react-scripts/scripts/eject.js: 195,196
src/create-react-app/packages/react-scripts/scripts/init.js: 235
src/create-react-app/packages/react-scripts/scripts/utils/verifyPackageTree.js: 132
src/jest/packages/jest-cli/src/get_no_test_found.js: 13
src/jest/packages/jest-cli/src/get_no_test_found_verbose.js: 40
src/jest/packages/jest-cli/src/pattern_prompt.js: 22
src/jest/packages/jest-config/src/deprecated.js: 50
src/jest/packages/jest-matcher-utils/src/index.js: 171
src/jest/packages/jest-validate/src/errors.js: 22,29
src/jest/packages/jest-validate/src/warnings.js: 31
src/react/scripts/jest/setupTests.js: 102
src/react/scripts/release/build-commands/add-git-tag.js: 21
src/react/scripts/rollup/build.js: 446
src/react/scripts/rollup/wrappers.js: 61,62,84,85
src/spectrum/admin/src/api/index.js: 36
src/spectrum/admin/src/components/avatar/index.js: 17,20,31,32,33
src/spectrum/admin/src/components/buttons/style.js: 30,56,56,62,63,73,81,92,102,112,116,125,131,147,156,158
src/spectrum/admin/src/components/formElements/style.js: 72,142
src/spectrum/admin/src/components/globals/index.js: 62,63,72,73,75,77,82,86,139,417,418,464,464,505,507
src/spectrum/admin/src/components/loading/style.js: 19,20,28,29,30,31,33,35
src/spectrum/admin/src/views/navbar/style.js: 50,87,89,89
src/spectrum/athena/index.js: 73
src/spectrum/athena/queues/community-invoice-paid.js: 48
src/spectrum/hermes/index.js: 91
src/spectrum/hermes/queues/send-new-message-email.js: 73,74,90,91
src/spectrum/hyperion/renderer/html-template.js: 77
src/spectrum/mercury/index.js: 19
src/spectrum/mobile/components/Avatar/style.js: 8,9,10,35,36,37,44,45,46
src/spectrum/mobile/components/Text/index.js: 36,39,39,41
src/spectrum/shared/graphql/index.js: 27
src/spectrum/src/components/avatar/style.js: 25,26,33,35,36,90,91,97,99,100,108,109,115,117,118
src/spectrum/src/components/buttons/style.js: 33,58,58,64,65,75,83,94,98,101,112,123,127,136,143,153,157,166,173,188,197,199
src/spectrum/src/components/chatInput/style.js: 114
src/spectrum/src/components/column/index.js: 18
src/spectrum/src/components/draftjs-editor/Image.js: 26,49
src/spectrum/src/components/draftjs-editor/style.js: 89,128
src/spectrum/src/components/emailInvitationForm/index.js: 153,154
src/spectrum/src/components/formElements/style.js: 74,144,217,219
src/spectrum/src/components/fullscreenView/style.js: 29
src/spectrum/src/components/globals/index.js: 99,100,109,110,112,114,119,123,176,492,539
src/spectrum/src/components/goop/index.js: 37
src/spectrum/src/components/linkPreview/style.js: 38
src/spectrum/src/components/loginButtonSet/style.js: 36,58
src/spectrum/src/components/menu/style.js: 15
src/spectrum/src/components/message/style.js: 15,15,19,74,75,151,222,222,242,242,275,275,283
src/spectrum/src/components/profile/coverPhoto.js: 17
src/spectrum/src/components/reaction/style.js: 19,28,39
src/spectrum/src/components/segmentedControl/index.js: 43
src/spectrum/src/components/themedSection/index.js: 27,37,46,55,69,78
src/spectrum/src/components/threadComposer/style.js: 46,59
src/spectrum/src/components/toasts/style.js: 53
src/spectrum/src/components/upsell/newUserUpsellStyles.js: 17
src/spectrum/src/components/upsell/style.js: 145,214,306,339
src/spectrum/src/views/communitySettings/style.js: 172
src/spectrum/src/views/dashboard/style.js: 162,205,219,807
src/spectrum/src/views/directMessages/components/style.js: 30
src/spectrum/src/views/directMessages/style.js: 48
src/spectrum/src/views/explore/style.js: 105,117
src/spectrum/src/views/login/style.js: 100,119,188
src/spectrum/src/views/navbar/style.js: 45,54,63,148,159,209
src/spectrum/src/views/newCommunity/components/createCommunityForm/style.js: 20,21
src/spectrum/src/views/newCommunity/index.js: 110,126,147
src/spectrum/src/views/newCommunity/style.js: 8
src/spectrum/src/views/newUserOnboarding/style.js: 87
src/spectrum/src/views/notifications/style.js: 126,137,138
src/spectrum/src/views/pages/pricing/style.js: 105,105,114,388,389,390
src/spectrum/src/views/pages/style.js: 191,409,439,669,684
src/spectrum/src/views/thread/style.js: 37,113,231,239,544
src/spectrum/src/views/threadSlider/style.js: 16
src/spectrum/vulcan/index.js: 27
src/vue/scripts/gen-release-note.js: 3,3
src/vue/scripts/verify-commit-msg.js: 10,12,13,15
src/vue/src/compiler/codegen/index.js: 75,77,301,303,305,326,353,399,459,460,482
src/vue/src/core/instance/render-helpers/render-static.js: 36,36
src/vue/src/core/util/debug.js: 92
src/vue/src/core/vdom/create-component.js: 191
src/vue/src/server/optimizing-compiler/codegen.js: 87,89,118
src/vue/src/server/optimizing-compiler/modules.js: 121
src/vue/src/server/template-renderer/index.js: 158
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const sonarjsRules: [string, TSESLint.Linter.RuleLevel][] = [
['no-identical-functions', 'error'],
['no-identical-expressions', 'error'],
['no-inverted-boolean-check', 'error'],
['no-nested-template-literals', 'error'],
['no-one-iteration-loop', 'error'],
['no-redundant-boolean', 'error'],
['no-redundant-jump', 'error'],
Expand Down
43 changes: 43 additions & 0 deletions src/rules/no-nested-template-literals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* eslint-plugin-sonarjs
* Copyright (C) 2018-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// https://jira.sonarsource.com/browse/RSPEC-4624

import { TSESTree } from '@typescript-eslint/experimental-utils';
import { Rule } from '../utils/types';

const message = 'Refactor this code to not use nested template literals.';

const rule: Rule.RuleModule = {
meta: {
type: 'suggestion',
},
create(context: Rule.RuleContext) {
return {
'TemplateLiteral TemplateLiteral': (node: TSESTree.Node) => {
context.report({
message,
node,
});
},
};
},
};

export = rule;
78 changes: 78 additions & 0 deletions tests/rules/no-nested-template-literals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* eslint-plugin-sonarjs
* Copyright (C) 2018-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* eslint-disable no-template-curly-in-string */
import { ruleTester } from '../rule-tester';
import * as rule from '../../src/rules/no-nested-template-literals';

ruleTester.run('Template literals should not be nested', rule, {
valid: [
{
code: 'let nestedMessage = `${count} ${color}`;',
},
{
code: 'let message = `I have ${color ? nestedMessage : count} apples`;',
},
],
invalid: [
{
code: 'let message = `I have ${color ? `${x ? `indeed 0` : count} ${color}` : count} apples`;',
errors: [
{
message: `Refactor this code to not use nested template literals.`,
line: 1,
endLine: 1,
column: 33,
endColumn: 69,
},
{
message: `Refactor this code to not use nested template literals.`,
line: 1,
endLine: 1,
column: 40,
endColumn: 50,
},
],
},
{
code: 'let message = `I have ${color ? `${count} ${color}` : count} apples`;',
errors: 1,
},
{
code: 'let message = `I have ${color ? `${x ? `indeed ${0}` : count} ${color}` : count} apples`;',
errors: 2,
},
{
code:
'function tag(strings, ...keys) {console.log(strings[2]);}\n' +
'let message1 = tag`I have ${color ? `${count} ${color}` : count} apples`;\n' +
'let message2 = tag`I have ${color ? tag`${count} ${color}` : count} apples`;',
errors: 2,
},
{
code: 'let message = `I have ${color ? `${count} ${color}` : `this is ${count}`} apples`;',
errors: 2,
},
{
code: 'let message = `I have ${`${count} ${color}`} ${`this is ${count}`} apples`;',
errors: 2,
},
],
});