Skip to content

Commit

Permalink
Update no-duplicate-string: Add ignoreStrings option (#405)
Browse files Browse the repository at this point in the history
  • Loading branch information
yassin-kammoun-sonarsource committed Jun 5, 2023
1 parent c053acc commit 8e3a683
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 11 deletions.
12 changes: 10 additions & 2 deletions docs/rules/no-duplicate-string.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ To prevent generating some false-positives, literals having less than 10 charact

## Configuration

Number of times a literal must be duplicated to trigger an issue. Default is 3.
Number of times a literal must be duplicated to trigger an issue. Default is `3`.

```json
{
"no-duplicate-string": "error",
"no-duplicate-string": ["error", 5]
"no-duplicate-string": ["error", { "threshold": 5 }]
}
```

Comma-separated list of strings that must be ignored. Default is `"application/json"`.

```json
{
"no-duplicate-string": "error",
"no-duplicate-string": ["error", { "ignoreStrings": "foo,bar,baz" }]
}
1 change: 0 additions & 1 deletion ruling/snapshots/no-duplicate-string
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ src/express/lib/response.js: 132,194
src/freeCodeCamp/common/app/redux/utils.test.js: 54
src/freeCodeCamp/common/app/routes/Challenges/utils/index.test.js: 24,25,28,90,112,164,274,321,322,350,405,477,597,811
src/freeCodeCamp/common/app/routes/Challenges/views/step/redux/step-challenge-epic.test.js: 48,62,69
src/freeCodeCamp/common/utils/ajax-stream.js: 283
src/freeCodeCamp/public/js/calculator.js: 9,16,149
src/freeCodeCamp/public/js/lib/loop-protect/loop-protect.js: 283
src/freeCodeCamp/seed/unpackedChallenge.js: 218,246
Expand Down
32 changes: 27 additions & 5 deletions src/rules/no-duplicate-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { issueLocation, report } from '../utils/locations';

// Number of times a literal must be duplicated to trigger an issue
const DEFAULT_THRESHOLD = 3;
const DEFAULT_IGNORE_STRINGS = 'application/json';
const MIN_LENGTH = 10;
const NO_SEPARATOR_REGEXP = /^\w*$/;
const EXCLUDED_CONTEXTS = [
Expand All @@ -36,7 +37,9 @@ const EXCLUDED_CONTEXTS = [
];
const message = 'Define a constant instead of duplicating this literal {{times}} times.';

type Options = (number | 'sonar-runtime')[];
type Options =
| [{ threshold?: number; ignoreStrings?: string } | undefined, 'sonar-runtime']
| [{ threshold?: number; ignoreStrings?: string } | undefined];
type Context = TSESLint.RuleContext<string, Options>;

const rule: TSESLint.RuleModule<string, Options> = {
Expand All @@ -52,16 +55,21 @@ const rule: TSESLint.RuleModule<string, Options> = {
url: docsUrl(__filename),
},
schema: [
{ type: 'integer', minimum: 2 },
{
type: 'object',
properties: {
threshold: { type: 'integer', minimum: 2 },
ignoreStrings: { type: 'string', default: DEFAULT_IGNORE_STRINGS },
},
},
{ enum: ['sonar-runtime'] /* internal parameter for rules having secondary locations */ },
],
},

create(context) {
const literalsByValue: Map<string, TSESTree.Literal[]> = new Map();
const threshold: number =
typeof context.options[0] === 'number' ? context.options[0] : DEFAULT_THRESHOLD;

const { threshold, ignoreStrings } = extractOptions(context);
const whitelist = ignoreStrings.split(',');
return {
Literal: (node: TSESTree.Node) => {
const literal = node as TSESTree.Literal;
Expand All @@ -74,6 +82,7 @@ const rule: TSESLint.RuleModule<string, Options> = {
const stringContent = literal.value.trim();

if (
!whitelist.includes(literal.value) &&
!isExcludedByUsageContext(context, literal) &&
stringContent.length >= MIN_LENGTH &&
!stringContent.match(NO_SEPARATOR_REGEXP)
Expand Down Expand Up @@ -130,4 +139,17 @@ function isObjectPropertyKey(parent: TSESTree.Node, literal: TSESTree.Literal) {
return parent.type === 'Property' && parent.key === literal;
}

function extractOptions(context: Context) {
let threshold: number = DEFAULT_THRESHOLD;
let ignoreStrings: string = DEFAULT_IGNORE_STRINGS;
const options = context.options[0];
if (typeof options?.threshold === 'number') {
threshold = options.threshold;
}
if (typeof options?.ignoreStrings === 'string') {
ignoreStrings = options.ignoreStrings;
}
return { threshold, ignoreStrings };
}

export = rule;
27 changes: 24 additions & 3 deletions tests/rules/no-duplicate-string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ ruleTester.run('no-duplicate-string', rule, {
console.log("some message");
console.log("some message");
console.log('some message');`,
options: [4],
options: [{ threshold: 4 }],
},
{
code: ` // too small
console.log("a&b");
console.log("a&b");
console.log("a&b");`,
},
{
code: ` // too small
Expand Down Expand Up @@ -151,6 +157,21 @@ ruleTester.run('no-duplicate-string', rule, {
}
`,
},
{
code: `
'application/json';
'application/json';
'application/json';;
`,
},
{
code: `
console.log('Hello world!');
console.log('Hello world!');
console.log('Hello world!');
`,
options: [{ threshold: 2, ignoreStrings: 'Hello world!' }, 'sonar-runtime'],
},
],
invalid: [
{
Expand Down Expand Up @@ -196,7 +217,7 @@ ruleTester.run('no-duplicate-string', rule, {
endColumn: 31,
},
],
options: [2, 'sonar-runtime'],
options: [{ threshold: 2 }, 'sonar-runtime'],
},
{
code: `
Expand Down Expand Up @@ -228,7 +249,7 @@ ruleTester.run('no-duplicate-string', rule, {
line: 2,
},
],
options: [2],
options: [{ threshold: 2 }],
},
{
code: `
Expand Down

0 comments on commit 8e3a683

Please sign in to comment.