Skip to content

Commit

Permalink
no-new-buffer: Use suggestion for unknown arguments (#1037)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Jan 19, 2021
1 parent db5a068 commit 88a724c
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 116 deletions.
10 changes: 7 additions & 3 deletions docs/rules/no-new-buffer.md
Expand Up @@ -2,25 +2,29 @@

Enforces the use of [Buffer.from](https://nodejs.org/api/buffer.html#buffer_class_method_buffer_from_array) and [Buffer.alloc()](https://nodejs.org/api/buffer.html#buffer_class_method_buffer_alloc_size_fill_encoding) instead of [new Buffer()](https://nodejs.org/api/buffer.html#buffer_new_buffer_array), which has been deprecated since Node.js 4.

This rule is fixable.

This rule is partly fixable.

## Fail

```js
const buffer = new Buffer('7468697320697320612074c3a97374', 'hex');
```

```
const buffer = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
```

```js
const buffer = new Buffer(10);
```


## Pass

```js
const buffer = Buffer.from('7468697320697320612074c3a97374', 'hex');
```

```js
const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])
```

Expand Down
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -139,7 +139,7 @@ Configure it in `package.json`.
- [no-lonely-if](docs/rules/no-lonely-if.md) - Disallow `if` statements as the only statement in `if` blocks without `else`. *(fixable)*
- [no-nested-ternary](docs/rules/no-nested-ternary.md) - Disallow nested ternary expressions. *(partly fixable)*
- [no-new-array](docs/rules/no-new-array.md) - Disallow `new Array()`. *(partly fixable)*
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)*
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(partly fixable)*
- [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal.
- [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) - Disallow the use of objects as default parameters.
- [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`.
Expand Down
16 changes: 3 additions & 13 deletions rules/new-for-builtins.js
Expand Up @@ -2,7 +2,7 @@
const getDocumentationUrl = require('./utils/get-documentation-url');
const builtins = require('./utils/builtins');
const isShadowed = require('./utils/is-shadowed');
const isNewExpressionWithParentheses = require('./utils/is-new-expression-with-parentheses');
const switchNewExpressionToCallExpression = require('./utils/switch-new-expression-to-call-expression');

const messages = {
enforce: 'Use `new {{name}}()` instead of `{{name}}()`.',
Expand Down Expand Up @@ -40,7 +40,7 @@ const create = context => {
}
},
NewExpression: node => {
const {callee, range} = node;
const {callee} = node;
const {name} = callee;

if (disallowNew.has(name) && !isShadowed(context.getScope(), callee)) {
Expand All @@ -52,17 +52,7 @@ const create = context => {

if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
problem.fix = function * (fixer) {
const [start] = range;
let end = start + 3; // `3` = length of `new`
const textAfter = sourceCode.text.slice(end);
const [leadingSpaces] = textAfter.match(/^\s*/);
end += leadingSpaces.length;

yield fixer.removeRange([start, end]);

if (!isNewExpressionWithParentheses(node, sourceCode)) {
yield fixer.insertTextAfter(node, '()');
}
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
};
}

Expand Down
85 changes: 63 additions & 22 deletions rules/no-new-buffer.js
@@ -1,40 +1,81 @@
'use strict';
const {getStaticValue} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const switchNewExpressionToCallExpression = require('./utils/switch-new-expression-to-call-expression');

const MESSAGE_ID = 'no-new-buffer';
const ERROR = 'error';
const ERROR_UNKNOWN = 'error-unknown';
const SUGGESTION = 'suggestion';
const messages = {
[MESSAGE_ID]: '`new Buffer()` is deprecated, use `Buffer.{{method}}()` instead.'
[ERROR]: '`new Buffer()` is deprecated, use `Buffer.{{method}}()` instead.',
[ERROR_UNKNOWN]: '`new Buffer()` is deprecated, use `Buffer.alloc()` or `Buffer.from()` instead.',
[SUGGESTION]: 'Switch to `Buffer.{{method}}()`.'
};

const inferMethod = arguments_ => {
if (arguments_.length > 0) {
const [firstArgument] = arguments_;
const inferMethod = (bufferArguments, scope) => {
if (bufferArguments.length !== 1) {
return 'from';
}

const [firstArgument] = bufferArguments;
if (firstArgument.type === 'SpreadElement') {
return;
}

if (firstArgument.type === 'ArrayExpression' || firstArgument.type === 'TemplateLiteral') {
return 'from';
}

const staticResult = getStaticValue(firstArgument, scope);
if (staticResult) {
const {value} = staticResult;
if (typeof value === 'number') {
return 'alloc';
}

if (
firstArgument.type === 'Literal' &&
typeof firstArgument.value === 'number'
typeof value === 'string' ||
Array.isArray(value)
) {
return 'alloc';
return 'from';
}
}

return 'from';
};

function fix(node, sourceCode, method) {
return function * (fixer) {
yield fixer.insertTextAfter(node.callee, `.${method}`);
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
};
}

const create = context => {
const sourceCode = context.getSourceCode();
return {
'NewExpression[callee.name="Buffer"]': node => {
const method = inferMethod(node.arguments);
const range = [
node.range[0],
node.callee.range[1]
];

context.report({
node,
messageId: MESSAGE_ID,
data: {method},
fix: fixer => fixer.replaceTextRange(range, `Buffer.${method}`)
});
const method = inferMethod(node.arguments, context.getScope());

if (method) {
context.report({
node,
messageId: ERROR,
data: {method},
fix: fix(node, sourceCode, method)
});
} else {
context.report({
node,
messageId: ERROR_UNKNOWN,
suggest: [
'from',
'alloc'
].map(method => ({
messageId: SUGGESTION,
data: {method},
fix: fix(node, sourceCode, method)
}))
});
}
}
};
};
Expand Down
17 changes: 17 additions & 0 deletions rules/utils/switch-new-expression-to-call-expression.js
@@ -0,0 +1,17 @@
'use strict';
const isNewExpressionWithParentheses = require('./is-new-expression-with-parentheses');

function * switchNewExpressionToCallExpression(node, sourceCode, fixer) {
const [start] = node.range;
let end = start + 3; // `3` = length of `new`
const textAfter = sourceCode.text.slice(end);
const [leadingSpaces] = textAfter.match(/^\s*/);
end += leadingSpaces.length;
yield fixer.removeRange([start, end]);

if (!isNewExpressionWithParentheses(node, sourceCode)) {
yield fixer.insertTextAfter(node, '()');
}
}

module.exports = switchNewExpressionToCallExpression;
129 changes: 60 additions & 69 deletions test/no-new-buffer.js
@@ -1,71 +1,67 @@
import {outdent} from 'outdent';
import {test} from './utils/test.js';

const allocError = {
messageId: 'no-new-buffer',
data: {method: 'alloc'}
};

const fromError = {
messageId: 'no-new-buffer',
data: {method: 'from'}
};

test({
test.snapshot({
valid: [
'const buf = Buffer.from(\'buf\')',
'const buf = Buffer.from(\'7468697320697320612074c3a97374\', \'hex\')',
'const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])',
'const buf = Buffer.alloc(10)'
'const buffer = Buffer',
'const buffer = new NotBuffer(1)',
'const buffer = Buffer.from(\'buf\')',
'const buffer = Buffer.from(\'7468697320697320612074c3a97374\', \'hex\')',
'const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])',
'const buffer = Buffer.alloc(10)'
],
invalid: [
{
code: 'const buf = new Buffer()',
errors: [fromError],
output: 'const buf = Buffer.from()'
},
{
code: 'const buf = new Buffer(\'buf\')',
errors: [fromError],
output: 'const buf = Buffer.from(\'buf\')'
},
{
code: 'const buf = new Buffer(\'7468697320697320612074c3a97374\', \'hex\')',
errors: [fromError],
output: 'const buf = Buffer.from(\'7468697320697320612074c3a97374\', \'hex\')'
},
{
code: 'const buf = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])',
errors: [fromError],
output: 'const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])'
},
{
code: 'const buf = new Buffer(10)',
errors: [allocError],
output: 'const buf = Buffer.alloc(10)'
},
{
code: outdent`
const ab = new ArrayBuffer(10);
const buf = new Buffer(ab, 0, 2);
`,
errors: [fromError],
output: outdent`
const ab = new ArrayBuffer(10);
const buf = Buffer.from(ab, 0, 2);
`
},
{
code: outdent`
const buf1 = new Buffer('buf');
const buf2 = new Buffer(buf1);
`,
errors: [fromError, fromError],
output: outdent`
const buf1 = Buffer.from('buf');
const buf2 = Buffer.from(buf1);
`
}
// `new Buffer(array)`
// https://nodejs.org/api/buffer.html#buffer_new_buffer_array
'const buffer = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])',
'const buffer = new Buffer([0x62, bar])',
outdent`
const array = [0x62];
const buffer = new Buffer(array);
`,

// `new Buffer(arrayBuffer[, byteOffset[, length]])`
// https://nodejs.org/api/buffer.html#buffer_new_buffer_arraybuffer_byteoffset_length
outdent`
const arrayBuffer = new ArrayBuffer(10);
const buffer = new Buffer(arrayBuffer);
`,
outdent`
const arrayBuffer = new ArrayBuffer(10);
const buffer = new Buffer(arrayBuffer, 0, );
`,
outdent`
const arrayBuffer = new ArrayBuffer(10);
const buffer = new Buffer(arrayBuffer, 0, 2);
`,

// `new Buffer(size)`
// https://nodejs.org/api/buffer.html#buffer_new_buffer_size
'const buffer = new Buffer(10);',
outdent`
const size = 10;
const buffer = new Buffer(size);
`,

// `new Buffer(string[, encoding])`
// https://nodejs.org/api/buffer.html#buffer_new_buffer_string_encoding
'const buffer = new Buffer("string");',
'const buffer = new Buffer("7468697320697320612074c3a97374", "hex")',
outdent`
const string = "string";
const buffer = new Buffer(string);
`,
// eslint-disable-next-line no-template-curly-in-string
'const buffer = new Buffer(`${unknown}`)',

// Unknown
'const buffer = new (Buffer)(unknown)',
'const buffer = new Buffer(unknown, 2)',
'const buffer = new Buffer(...unknown)',

// Misc
'const buffer = new /* comment */ Buffer()',
'const buffer = new /* comment */ Buffer'
]
});

Expand All @@ -74,13 +70,8 @@ test.typescript({
invalid: [
{
code: 'new Buffer(input, encoding);',
errors: [fromError],
output: 'Buffer.from(input, encoding);'
output: 'Buffer.from(input, encoding);',
errors: 1
}
]
});

test.snapshot([
'const buf = new Buffer()',
'const buf = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])'
]);

0 comments on commit 88a724c

Please sign in to comment.