diff --git a/etc/eslint/rules/stdlib.js b/etc/eslint/rules/stdlib.js index a55ab53096ef..f8a35c60aba8 100644 --- a/etc/eslint/rules/stdlib.js +++ b/etc/eslint/rules/stdlib.js @@ -4157,6 +4157,25 @@ rules[ 'stdlib/no-dynamic-require' ] = 'error'; */ rules[ 'stdlib/no-empty-comments' ] = 'error'; +/** +* Disallow string concatenation in error messages. +* +* @name no-error-string-concat +* @memberof rules +* @type {string} +* @default 'error' +* +* @example +* // Bad... +* throw new Error( 'invalid argument. Value: `' + value + '`.' ); +* +* @example +* // Good... +* throw new Error( 'unexpected error.' ); +* throw new Error( format( 'invalid argument. Value: `%s`.', value ) ); +*/ +rules[ 'stdlib/no-error-string-concat' ] = 'error'; + /** * Enforce that `require()` expressions are not immediately invoked. * diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js index 817b943efba0..0560718171bd 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js @@ -882,6 +882,15 @@ setReadOnly( rules, 'no-dynamic-require', require( '@stdlib/_tools/eslint/rules/ */ setReadOnly( rules, 'no-empty-comments', require( '@stdlib/_tools/eslint/rules/no-empty-comments' ) ); +/** +* @name no-error-string-concat +* @memberof rules +* @readonly +* @type {Function} +* @see {@link module:@stdlib/_tools/eslint/rules/no-error-string-concat} +*/ +setReadOnly( rules, 'no-error-string-concat', require( '@stdlib/_tools/eslint/rules/no-error-string-concat' ) ); + /** * @name no-immediate-require * @memberof rules diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/README.md b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/README.md new file mode 100644 index 000000000000..fcf8555bb5ec --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/README.md @@ -0,0 +1,120 @@ + + +# no-error-string-concat + +> [ESLint rule][eslint-rules] disallowing string concatenation in error messages. + +
+ +
+ + + +
+ +## Usage + +```javascript +var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' ); +``` + +#### rule + +[ESLint rule][eslint-rules] disallowing string concatenation in error messages. + +**Bad**: + + + + + +```javascript +throw new Error( 'invalid argument. Value: `' + value + '`.' ); +``` + +**Good**: + + + +```javascript +var format = require( '@stdlib/string/format' ); + +throw new Error( format( 'invalid argument. Value: `%s`.', value ) ); +``` + +
+ + + +
+ +## Examples + +```javascript +var Linter = require( 'eslint' ).Linter; +var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' ); + +var linter = new Linter(); +linter.defineRule( 'no-error-string-concat', rule ); + +var code = 'throw new Error( \'invalid argument. Value: `\' + value + \'`.\' );'; +var result = linter.verify( code, { + 'rules': { + 'no-error-string-concat': 'error' + } +}); +/* returns + [ + { + 'ruleId': 'no-error-string-concat', + 'severity': 2, + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'line': 1, + 'column': 18, + 'nodeType': 'BinaryExpression', + 'endLine': 1, + 'endColumn': 61 + } + ] +*/ +``` + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/examples/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/examples/index.js new file mode 100644 index 000000000000..e82f7a5c13c4 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/examples/index.js @@ -0,0 +1,47 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var Linter = require( 'eslint' ).Linter; +var rule = require( './../lib' ); + +var linter = new Linter(); +linter.defineRule( 'no-error-string-concat', rule ); + +var code = 'throw new Error( \'invalid argument. Value: `\' + value + \'`.\' );'; +var result = linter.verify( code, { + 'rules': { + 'no-error-string-concat': 'error' + } +}); +console.log( result ); +/* => + [ + { + 'ruleId': 'no-error-string-concat', + 'severity': 2, + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'line': 1, + 'column': 18, + 'nodeType': 'BinaryExpression', + 'endLine': 1, + 'endColumn': 61 + } + ] +*/ diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/index.js new file mode 100644 index 000000000000..52587016940d --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/index.js @@ -0,0 +1,39 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* ESLint rule disallowing string concatenation in error messages. +* +* @module @stdlib/_tools/eslint/rules/no-error-string-concat +* +* @example +* var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' ); +* +* console.log( rule ); +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/main.js new file mode 100644 index 000000000000..00e544a04ee4 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/lib/main.js @@ -0,0 +1,145 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// VARIABLES // + +var ERROR_CONSTRUCTORS = [ + 'Error', + 'TypeError', + 'RangeError', + 'SyntaxError', + 'ReferenceError', + 'URIError', + 'EvalError' +]; +var rule; + + +// FUNCTIONS // + +/** +* Checks whether a node is a BinaryExpression using the addition operator. +* +* @private +* @param {ASTNode} node - AST node +* @returns {boolean} boolean indicating whether the node is a binary addition expression +*/ +function isBinaryAddition( node ) { + return node.type === 'BinaryExpression' && node.operator === '+'; +} + +/** +* Checks whether a callee name is an error constructor. +* +* @private +* @param {string} name - callee name +* @returns {boolean} boolean indicating whether the name is an error constructor +*/ +function isErrorConstructor( name ) { + var i; + for ( i = 0; i < ERROR_CONSTRUCTORS.length; i++ ) { + if ( name === ERROR_CONSTRUCTORS[ i ] ) { + return true; + } + } + return false; +} + +/** +* Rule for validating that `@stdlib/string/format` is used instead of string concatenation in error messages. +* +* @param {Object} context - ESLint context +* @returns {Object} validators +*/ +function main( context ) { + /** + * Reports the error message. + * + * @private + * @param {ASTNode} node - node to report + */ + function report( node ) { + context.report({ + 'node': node, + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.' + }); + } + + /** + * Checks whether an error constructor call uses string concatenation in its first argument. + * + * @private + * @param {ASTNode} node - CallExpression or NewExpression node to examine + */ + function validate( node ) { + var firstArg; + var callee; + var name; + + callee = node.callee; + + // Get the constructor name: + if ( callee.type === 'Identifier' ) { + name = callee.name; + } else { + return; + } + + // Check if this is a call to an error constructor: + if ( !isErrorConstructor( name ) ) { + return; + } + + // Check if there is at least one argument: + if ( node.arguments.length === 0 ) { + return; + } + + firstArg = node.arguments[ 0 ]; + + // Check if the first argument uses the addition operator: + if ( isBinaryAddition( firstArg ) ) { + report( firstArg ); + } + } + + return { + 'NewExpression': validate, + 'CallExpression': validate + }; +} + + +// MAIN // + +rule = { + 'meta': { + 'docs': { + 'description': 'disallow string concatenation in error messages' + }, + 'schema': [] + }, + 'create': main +}; + + +// EXPORTS // + +module.exports = rule; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/package.json b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/package.json new file mode 100644 index 000000000000..b012505ef738 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/package.json @@ -0,0 +1,53 @@ +{ + "name": "@stdlib/_tools/eslint/rules/no-error-string-concat", + "version": "0.0.0", + "description": "ESLint rule disallowing string concatenation in error messages.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "bin": {}, + "main": "./lib", + "directories": { + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "keywords": [ + "stdlib", + "tools", + "tool", + "eslint", + "lint", + "custom", + "rules", + "rule", + "plugin", + "error", + "string", + "concatenation", + "format" + ] +} diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/invalid.js new file mode 100644 index 000000000000..dde04596f9b2 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/invalid.js @@ -0,0 +1,122 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var invalid = []; + +// Simple string concatenation in Error: +var test = { + 'code': 'throw new Error( \'invalid argument. Value: `\' + value + \'`.\' );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in TypeError: +test = { + 'code': 'throw new TypeError( \'expected a string. Value: `\' + x + \'`.\' );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in RangeError: +test = { + 'code': 'throw new RangeError( idx + \' is out of bounds.\' );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// Error called without new: +test = { + 'code': 'throw Error( \'invalid argument. Value: `\' + value + \'`.\' );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in SyntaxError: +test = { + 'code': 'throw new SyntaxError( \'unexpected token: \' + token );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in ReferenceError: +test = { + 'code': 'throw new ReferenceError( name + \' is not defined.\' );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in URIError: +test = { + 'code': 'throw new URIError( \'invalid URI: \' + uri );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + +// String concatenation in EvalError: +test = { + 'code': 'throw new EvalError( \'eval error: \' + msg );', + 'errors': [ + { + 'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.', + 'type': 'BinaryExpression' + } + ] +}; +invalid.push( test ); + + +// EXPORTS // + +module.exports = invalid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/unvalidated.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/unvalidated.js new file mode 100644 index 000000000000..739356344ba9 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/unvalidated.js @@ -0,0 +1,56 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var valid = []; + +// String concatenation in non-error constructor: +var test = { + 'code': 'new MyClass( \'prefix\' + value );' +}; +valid.push( test ); + +// String concatenation in regular function call: +test = { + 'code': 'console.log( \'value: \' + x );' +}; +valid.push( test ); + +// String concatenation assigned to variable, then used in error: +test = { + 'code': 'var msg = \'invalid: \' + x;\nthrow new Error( msg );' +}; +valid.push( test ); + +// Member expression (not a simple identifier): +test = { + 'code': 'throw new errors.CustomError( \'prefix\' + value );' +}; +valid.push( test ); + +// String concatenation in second argument: +test = { + 'code': 'throw new Error( \'message\', \'cause: \' + x );' +}; +valid.push( test ); + + +// EXPORTS // + +module.exports = valid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/valid.js new file mode 100644 index 000000000000..79aaefed8125 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/fixtures/valid.js @@ -0,0 +1,62 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var valid = []; + +// Using a string literal: +var test = { + 'code': 'throw new Error( \'unexpected error.\' );' +}; +valid.push( test ); + +// Using format function: +test = { + 'code': 'throw new Error( format( \'invalid argument. First argument must be a number. Value: `%s`.\', value ) );' +}; +valid.push( test ); + +// Using a variable: +test = { + 'code': 'var msg = \'error message\';\nthrow new Error( msg );' +}; +valid.push( test ); + +// Error constructor without arguments: +test = { + 'code': 'throw new Error();' +}; +valid.push( test ); + +// TypeError with string literal: +test = { + 'code': 'throw new TypeError( \'expected a string.\' );' +}; +valid.push( test ); + +// RangeError with format: +test = { + 'code': 'throw new RangeError( format( \'index out of bounds. Value: `%d`.\', idx ) );' +}; +valid.push( test ); + + +// EXPORTS // + +module.exports = valid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/test.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/test.js new file mode 100644 index 000000000000..c204a6820904 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-error-string-concat/test/test.js @@ -0,0 +1,86 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var RuleTester = require( 'eslint' ).RuleTester; +var rule = require( './../lib' ); + + +// FIXTURES // + +var valid = require( './fixtures/valid.js' ); +var invalid = require( './fixtures/invalid.js' ); +var unvalidated = require( './fixtures/unvalidated.js' ); + + +// TESTS // + +tape( 'main export is an object', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof rule, 'object', 'main export is an object' ); + t.end(); +}); + +tape( 'the rule positively validates code using `format` instead of string concatenation in error messages', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-error-string-concat', rule, { + 'valid': valid, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the rule negatively validates code using string concatenation instead of `format` in error messages', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-error-string-concat', rule, { + 'valid': [], + 'invalid': invalid + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the rule does not validate string concatenation outside of error constructors', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-error-string-concat', rule, { + 'valid': unvalidated, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +});