Skip to content

Commit ec526fd

Browse files
Planeshifterkgryte
andauthored
feat: add ESLint rule disallowing string concatenation in error messages
PR-URL: #9034 Closes: stdlib-js/metr-issue-tracker#128 Co-authored-by: Athan Reines <kgryte@gmail.com> Reviewed-by: Athan Reines <kgryte@gmail.com>
1 parent add570e commit ec526fd

File tree

11 files changed

+758
-0
lines changed

11 files changed

+758
-0
lines changed

etc/eslint/rules/stdlib.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4157,6 +4157,25 @@ rules[ 'stdlib/no-dynamic-require' ] = 'error';
41574157
*/
41584158
rules[ 'stdlib/no-empty-comments' ] = 'error';
41594159

4160+
/**
4161+
* Disallow string concatenation in error messages.
4162+
*
4163+
* @name no-error-string-concat
4164+
* @memberof rules
4165+
* @type {string}
4166+
* @default 'error'
4167+
*
4168+
* @example
4169+
* // Bad...
4170+
* throw new Error( 'invalid argument. Value: `' + value + '`.' );
4171+
*
4172+
* @example
4173+
* // Good...
4174+
* throw new Error( 'unexpected error.' );
4175+
* throw new Error( format( 'invalid argument. Value: `%s`.', value ) );
4176+
*/
4177+
rules[ 'stdlib/no-error-string-concat' ] = 'error';
4178+
41604179
/**
41614180
* Enforce that `require()` expressions are not immediately invoked.
41624181
*

lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,15 @@ setReadOnly( rules, 'no-dynamic-require', require( '@stdlib/_tools/eslint/rules/
891891
*/
892892
setReadOnly( rules, 'no-empty-comments', require( '@stdlib/_tools/eslint/rules/no-empty-comments' ) );
893893

894+
/**
895+
* @name no-error-string-concat
896+
* @memberof rules
897+
* @readonly
898+
* @type {Function}
899+
* @see {@link module:@stdlib/_tools/eslint/rules/no-error-string-concat}
900+
*/
901+
setReadOnly( rules, 'no-error-string-concat', require( '@stdlib/_tools/eslint/rules/no-error-string-concat' ) );
902+
894903
/**
895904
* @name no-immediate-require
896905
* @memberof rules
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2025 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# no-error-string-concat
22+
23+
> [ESLint rule][eslint-rules] disallowing string concatenation in error messages.
24+
25+
<section class="intro">
26+
27+
</section>
28+
29+
<!-- /.intro -->
30+
31+
<section class="usage">
32+
33+
## Usage
34+
35+
```javascript
36+
var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' );
37+
```
38+
39+
#### rule
40+
41+
[ESLint rule][eslint-rules] disallowing string concatenation in error messages.
42+
43+
**Bad**:
44+
45+
<!-- run-disable -->
46+
47+
<!-- eslint-disable stdlib/no-error-string-concat -->
48+
49+
```javascript
50+
throw new Error( 'invalid argument. Value: `' + value + '`.' );
51+
```
52+
53+
**Good**:
54+
55+
<!-- run-disable -->
56+
57+
```javascript
58+
var format = require( '@stdlib/string/format' );
59+
60+
throw new Error( format( 'invalid argument. Value: `%s`.', value ) );
61+
```
62+
63+
</section>
64+
65+
<!-- /.usage -->
66+
67+
<section class="examples">
68+
69+
## Examples
70+
71+
```javascript
72+
var Linter = require( 'eslint' ).Linter;
73+
var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' );
74+
75+
var linter = new Linter();
76+
linter.defineRule( 'no-error-string-concat', rule );
77+
78+
var code = 'throw new Error( \'invalid argument. Value: `\' + value + \'`.\' );';
79+
var result = linter.verify( code, {
80+
'rules': {
81+
'no-error-string-concat': 'error'
82+
}
83+
});
84+
/* returns
85+
[
86+
{
87+
'ruleId': 'no-error-string-concat',
88+
'severity': 2,
89+
'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.',
90+
'line': 1,
91+
'column': 18,
92+
'nodeType': 'BinaryExpression',
93+
'endLine': 1,
94+
'endColumn': 61
95+
}
96+
]
97+
*/
98+
```
99+
100+
</section>
101+
102+
<!-- /.examples -->
103+
104+
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. -->
105+
106+
<section class="related">
107+
108+
</section>
109+
110+
<!-- /.related -->
111+
112+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
113+
114+
<section class="links">
115+
116+
[eslint-rules]: https://eslint.org/docs/developer-guide/working-with-rules
117+
118+
</section>
119+
120+
<!-- /.links -->
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
var Linter = require( 'eslint' ).Linter;
22+
var rule = require( './../lib' );
23+
24+
var linter = new Linter();
25+
linter.defineRule( 'no-error-string-concat', rule );
26+
27+
var code = 'throw new Error( \'invalid argument. Value: `\' + value + \'`.\' );';
28+
var result = linter.verify( code, {
29+
'rules': {
30+
'no-error-string-concat': 'error'
31+
}
32+
});
33+
console.log( result );
34+
/* =>
35+
[
36+
{
37+
'ruleId': 'no-error-string-concat',
38+
'severity': 2,
39+
'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.',
40+
'line': 1,
41+
'column': 18,
42+
'nodeType': 'BinaryExpression',
43+
'endLine': 1,
44+
'endColumn': 61
45+
}
46+
]
47+
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
/**
22+
* ESLint rule disallowing string concatenation in error messages.
23+
*
24+
* @module @stdlib/_tools/eslint/rules/no-error-string-concat
25+
*
26+
* @example
27+
* var rule = require( '@stdlib/_tools/eslint/rules/no-error-string-concat' );
28+
*
29+
* console.log( rule );
30+
*/
31+
32+
// MODULES //
33+
34+
var main = require( './main.js' );
35+
36+
37+
// EXPORTS //
38+
39+
module.exports = main;
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// VARIABLES //
22+
23+
var ERROR_CONSTRUCTORS = [
24+
'Error',
25+
'TypeError',
26+
'RangeError',
27+
'SyntaxError',
28+
'ReferenceError',
29+
'URIError',
30+
'EvalError'
31+
];
32+
var rule;
33+
34+
35+
// FUNCTIONS //
36+
37+
/**
38+
* Checks whether a node is a BinaryExpression using the addition operator.
39+
*
40+
* @private
41+
* @param {ASTNode} node - AST node
42+
* @returns {boolean} boolean indicating whether the node is a binary addition expression
43+
*/
44+
function isBinaryAddition( node ) {
45+
return node.type === 'BinaryExpression' && node.operator === '+';
46+
}
47+
48+
/**
49+
* Checks whether a callee name is an error constructor.
50+
*
51+
* @private
52+
* @param {string} name - callee name
53+
* @returns {boolean} boolean indicating whether the name is an error constructor
54+
*/
55+
function isErrorConstructor( name ) {
56+
var i;
57+
for ( i = 0; i < ERROR_CONSTRUCTORS.length; i++ ) {
58+
if ( name === ERROR_CONSTRUCTORS[ i ] ) {
59+
return true;
60+
}
61+
}
62+
return false;
63+
}
64+
65+
/**
66+
* Rule for validating that `@stdlib/string/format` is used instead of string concatenation in error messages.
67+
*
68+
* @param {Object} context - ESLint context
69+
* @returns {Object} validators
70+
*/
71+
function main( context ) {
72+
/**
73+
* Reports the error message.
74+
*
75+
* @private
76+
* @param {ASTNode} node - node to report
77+
*/
78+
function report( node ) {
79+
context.report({
80+
'node': node,
81+
'message': 'Use `@stdlib/string/format` instead of string concatenation in error messages.'
82+
});
83+
}
84+
85+
/**
86+
* Checks whether an error constructor call uses string concatenation in its first argument.
87+
*
88+
* @private
89+
* @param {ASTNode} node - CallExpression or NewExpression node to examine
90+
*/
91+
function validate( node ) {
92+
var firstArg;
93+
var callee;
94+
var name;
95+
96+
callee = node.callee;
97+
98+
// Get the constructor name:
99+
if ( callee.type === 'Identifier' ) {
100+
name = callee.name;
101+
} else {
102+
return;
103+
}
104+
105+
// Check if this is a call to an error constructor:
106+
if ( !isErrorConstructor( name ) ) {
107+
return;
108+
}
109+
110+
// Check if there is at least one argument:
111+
if ( node.arguments.length === 0 ) {
112+
return;
113+
}
114+
115+
firstArg = node.arguments[ 0 ];
116+
117+
// Check if the first argument uses the addition operator:
118+
if ( isBinaryAddition( firstArg ) ) {
119+
report( firstArg );
120+
}
121+
}
122+
123+
return {
124+
'NewExpression': validate,
125+
'CallExpression': validate
126+
};
127+
}
128+
129+
130+
// MAIN //
131+
132+
rule = {
133+
'meta': {
134+
'docs': {
135+
'description': 'disallow string concatenation in error messages'
136+
},
137+
'schema': []
138+
},
139+
'create': main
140+
};
141+
142+
143+
// EXPORTS //
144+
145+
module.exports = rule;

0 commit comments

Comments
 (0)