Skip to content

Commit

Permalink
Add prefer-string-slice rule (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
futpib authored and sindresorhus committed Sep 25, 2019
1 parent b5cf874 commit cbd5dfc
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 0 deletions.
20 changes: 20 additions & 0 deletions docs/rules/prefer-string-slice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Prefer `String#slice()` over `String#substr()` and `String#substring()`

[`String#substr()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) and [`String#substring()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) are the two lesser known legacy ways to slice a string. It's better to use [`String#slice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) as it's a more popular option with clearer behavior that has a consistent [`Array` counterpart](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice).

This rule is fixible when no arguments are passed to the method.


## Fail

```js
foo.substr(1, 2);
foo.substring(1, 3);
```


## Pass

```js
foo.slice(1, 3);
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ module.exports = {
'unicorn/prefer-reflect-apply': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-starts-ends-with': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-text-content': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/prevent-abbreviations': 'error',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"ci-info": "^2.0.0",
"clean-regexp": "^1.0.0",
"eslint-ast-utils": "^1.1.0",
"eslint-template-visitor": "^1.0.0",
"import-modules": "^1.1.0",
"lodash.camelcase": "^4.3.0",
"lodash.defaultsdeep": "^4.6.1",
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Configure it in `package.json`.
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-spread": "error",
"unicorn/prefer-starts-ends-with": "error",
"unicorn/prefer-string-slice": "error",
"unicorn/prefer-text-content": "error",
"unicorn/prefer-type-error": "error",
"unicorn/prevent-abbreviations": "error",
Expand Down Expand Up @@ -123,6 +124,7 @@ Configure it in `package.json`.
- [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) - Prefer `Reflect.apply()` over `Function#apply()`. *(fixable)*
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)*
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over more complex alternatives.
- [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)*
- [prefer-text-content](docs/rules/prefer-text-content.md) - Prefer `.textContent` over `.innerText`. *(fixable)*
- [prefer-type-error](docs/rules/prefer-type-error.md) - Enforce throwing `TypeError` in type checking conditions. *(fixable)*
- [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations. *(partly fixable)*
Expand Down
64 changes: 64 additions & 0 deletions rules/prefer-string-slice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';
const eslintTemplateVisitor = require('eslint-template-visitor');
const getDocsUrl = require('./utils/get-docs-url');

const templates = eslintTemplateVisitor();

const objectVariable = templates.variable();
const argumentsVariable = templates.spreadVariable();

const substrCallTemplate = templates.template`${objectVariable}.substr(${argumentsVariable})`;
const substringCallTemplate = templates.template`${objectVariable}.substring(${argumentsVariable})`;

const create = context => {
const sourceCode = context.getSourceCode();

return templates.visitor({
[substrCallTemplate](node) {
const objectNode = substrCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substrCallTemplate.context.getMatch(argumentsVariable);

const problem = {
node,
message: 'Prefer `String#slice()` over `String#substr()`.'
};

const canFix = argumentNodes.length === 0;

if (canFix) {
problem.fix = fixer => fixer.replaceText(node, sourceCode.getText(objectNode) + '.slice()');
}

context.report(problem);
},

[substringCallTemplate](node) {
const objectNode = substringCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substringCallTemplate.context.getMatch(argumentsVariable);

const problem = {
node,
message: 'Prefer `String#slice()` over `String#substring()`.'
};

const canFix = argumentNodes.length === 0;

if (canFix) {
problem.fix = fixer => fixer.replaceText(node, sourceCode.getText(objectNode) + '.slice()');
}

context.report(problem);
}
});
};

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocsUrl(__filename)
},
fixable: 'code'
}
};
83 changes: 83 additions & 0 deletions test/prefer-string-slice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import test from 'ava';
import avaRuleTester from 'eslint-ava-rule-tester';
import rule from '../rules/prefer-string-slice';

const ruleTester = avaRuleTester(test, {
env: {
es6: true
}
});

const errors = [{
ruleId: 'prefer-string-slice'
}];

ruleTester.run('prefer-string-slice', rule, {
valid: [
'const substr = foo.substr',
'const substring = foo.substring',

'foo.slice()',
'foo.slice(0)',
'foo.slice(1, 2)',
'foo.slice(-3, -2)'
],

invalid: [
{
code: 'foo.substr()',
output: 'foo.slice()',
errors
},
{
code: '"foo".substr()',
output: '"foo".slice()',
errors
},

{
code: 'foo.substr(start)',
errors
},
{
code: '"foo".substr(1)',
errors
},
{
code: 'foo.substr(start, length)',
errors
},
{
code: '"foo".substr(1, 2)',
errors
},

{
code: 'foo.substring()',
output: 'foo.slice()',
errors
},
{
code: '"foo".substring()',
output: '"foo".slice()',
errors
},

{
code: 'foo.substring(start)',
errors
},
{
code: '"foo".substring(1)',
errors
},
{
code: 'foo.substring(start, end)',
errors
},
{
code: '"foo".substring(1, 3)',
errors
}
]
});

0 comments on commit cbd5dfc

Please sign in to comment.