From b17387c34d86c353434e40aa748bb5182033cfd6 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 22 Aug 2026 01:13:39 -0500 Subject: [PATCH 1/6] feat: add `no-unnecessary-format` ESLint rule Adds a custom ESLint rule which disallows calls to the format function exported by `@stdlib/string/format` when the format string does not contain any placeholders. Such calls are unnecessary, as the format function simply returns the input string unchanged. The rule is auto-fixable: the fixer replaces the format call with the string literal argument. Co-Authored-By: Claude Fable 5 --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: na - task: lint_markdown_docs status: na - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- etc/eslint/rules/stdlib.js | 22 +- .../@stdlib/_tools/eslint/rules/lib/index.js | 9 + .../rules/no-unnecessary-format/README.md | 136 +++++++++++++ .../no-unnecessary-format/examples/index.js | 52 +++++ .../rules/no-unnecessary-format/lib/index.js | 39 ++++ .../rules/no-unnecessary-format/lib/main.js | 191 ++++++++++++++++++ .../rules/no-unnecessary-format/package.json | 66 ++++++ .../test/fixtures/invalid.js | 147 ++++++++++++++ .../test/fixtures/unvalidated.js | 57 ++++++ .../test/fixtures/valid.js | 119 +++++++++++ .../rules/no-unnecessary-format/test/test.js | 98 +++++++++ 11 files changed, 935 insertions(+), 1 deletion(-) create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/README.md create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/examples/index.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/index.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/package.json create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/test.js diff --git a/etc/eslint/rules/stdlib.js b/etc/eslint/rules/stdlib.js index 1c84b79de46c..cf117b2c65b6 100644 --- a/etc/eslint/rules/stdlib.js +++ b/etc/eslint/rules/stdlib.js @@ -1,4 +1,4 @@ -/* eslint-disable stdlib/jsdoc-doctest-marker, stdlib/jsdoc-doctest, stdlib/jsdoc-example-require-spacing, stdlib/jsdoc-no-tabs */ +/* eslint-disable stdlib/jsdoc-doctest-marker, stdlib/jsdoc-example-require-spacing, stdlib/jsdoc-no-tabs */ /** * @license Apache-2.0 @@ -4442,6 +4442,26 @@ rules[ 'stdlib/no-nested-require' ] = 'error'; */ rules[ 'stdlib/no-unnecessary-nested-functions' ] = 'error'; +/** +* Disallow format calls that do not perform string interpolation. +* +* @name no-unnecessary-format +* @memberof rules +* @type {string} +* @default 'error' +* +* @example +* // Bad... +* var format = require( '@stdlib/string/format' ); +* +* throw new Error( format( 'invalid argument.' ) ); +* +* @example +* // Good... +* throw new Error( 'invalid argument.' ); +*/ +rules[ 'stdlib/no-unnecessary-format' ] = 'error'; + /** * Disallow the use of the `new Array()` constructor. * 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 c90cab4a4289..25ac65d000ac 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js @@ -1017,6 +1017,15 @@ setReadOnly( rules, 'no-single-property-require', require( '@stdlib/_tools/eslin */ setReadOnly( rules, 'no-unassigned-require', require( '@stdlib/_tools/eslint/rules/no-unassigned-require' ) ); +/** +* @name no-unnecessary-format +* @memberof rules +* @readonly +* @type {Function} +* @see {@link module:@stdlib/_tools/eslint/rules/no-unnecessary-format} +*/ +setReadOnly( rules, 'no-unnecessary-format', require( '@stdlib/_tools/eslint/rules/no-unnecessary-format' ) ); + /** * @name no-unnecessary-nested-functions * @memberof rules diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/README.md b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/README.md new file mode 100644 index 000000000000..0de4bc96bbd7 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/README.md @@ -0,0 +1,136 @@ + + +# no-unnecessary-format + +> [ESLint rule][eslint-rules] disallowing format calls that do not perform string interpolation. + +
+ +
+ + + +
+ +## Usage + +```javascript +var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-format' ); +``` + +#### rule + +[ESLint rule][eslint-rules] disallowing calls to the format function exported by [`@stdlib/string/format`][@stdlib/string/format] when the format string does not contain any placeholders. Such calls are unnecessary, as the format function simply returns the input string unchanged. + +**Bad**: + + + + + +```javascript +var format = require( '@stdlib/string/format' ); + +throw new Error( format( 'invalid argument.' ) ); +``` + +**Good**: + + + +```javascript +throw new Error( 'invalid argument.' ); +``` + +
+ + + +
+ +
+ + + +
+ +## Examples + + + +```javascript +var Linter = require( 'eslint' ).Linter; +var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-format' ); + +var linter = new Linter(); + +var code = [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'throw new Error( format( \'invalid argument.\' ) );' +].join( '\n' ); + +linter.defineRule( 'no-unnecessary-format', rule ); + +var result = linter.verify( code, { + 'rules': { + 'no-unnecessary-format': 'error' + } +}); +/* returns + [ + { + 'ruleId': 'no-unnecessary-format', + 'severity': 2, + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'line': 3, + 'column': 18, + 'nodeType': 'CallExpression', + 'endLine': 3, + 'endColumn': 47 + } + ] +*/ +``` + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/examples/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/examples/index.js new file mode 100644 index 000000000000..05b7adce2e11 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/examples/index.js @@ -0,0 +1,52 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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(); +var code = [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'throw new Error( format( \'invalid argument.\' ) );' +].join( '\n' ); + +linter.defineRule( 'no-unnecessary-format', rule ); + +var results = linter.verify( code, { + 'rules': { + 'no-unnecessary-format': 'error' + } +}); +console.log( results ); +/* => + [ + { + 'ruleId': 'no-unnecessary-format', + 'severity': 2, + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'line': 3, + 'column': 18, + 'nodeType': 'CallExpression', + 'endLine': 3, + 'endColumn': 47 + } + ] +*/ diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/index.js new file mode 100644 index 000000000000..83700c0f6778 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/index.js @@ -0,0 +1,39 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 format calls that do not perform string interpolation. +* +* @module @stdlib/_tools/eslint/rules/no-unnecessary-format +* +* @example +* var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-format' ); +* +* console.log( rule ); +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js new file mode 100644 index 000000000000..3954c479df12 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js @@ -0,0 +1,191 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 formatTokenize = require( '@stdlib/string/base/format-tokenize' ); + + +// VARIABLES // + +var PKG = '@stdlib/string/format'; +var rule; + + +// FUNCTIONS // + +/** +* Returns a boolean indicating whether a provided identifier name resolves to the format function exported by `@stdlib/string/format`. +* +* ## Notes +* +* - The function walks parent scopes and inspects the **nearest** binding for the provided name, thus guarding against shadowed symbols. +* +* @private +* @param {Object} scope - scope +* @param {string} name - identifier name +* @returns {boolean} boolean indicating whether an identifier name resolves to the format function +*/ +function isFormat( scope, name ) { + var v; + + while ( scope ) { + if ( scope.set.has( name ) ) { + v = scope.set.get( name ); + if ( + v.defs.length > 0 && + v.defs[ 0 ].node.type === 'VariableDeclarator' && + v.defs[ 0 ].node.init && + v.defs[ 0 ].node.init.type === 'CallExpression' && + v.defs[ 0 ].node.init.callee.name === 'require' && + v.defs[ 0 ].node.init.arguments.length > 0 && + v.defs[ 0 ].node.init.arguments[ 0 ].value === PKG + ) { + return true; + } + return false; + } + scope = scope.upper; + } + return false; +} + +/** +* Returns a boolean indicating whether a format string contains any placeholders. +* +* @private +* @param {string} str - format string +* @returns {boolean} boolean indicating whether a format string contains placeholders +*/ +function hasPlaceholders( str ) { + var tokens; + var i; + + tokens = formatTokenize( str ); + for ( i = 0; i < tokens.length; i++ ) { + if ( typeof tokens[ i ] !== 'string' ) { + return true; + } + } + return false; +} + +/** +* Returns a boolean indicating whether a format call can be safely auto-fixed. +* +* ## Notes +* +* - Auto-fixing is unsafe when the format string contains `%%` escape sequences, as `format` converts `%%` to `%` at runtime, so replacing the call with the raw string literal would change the program's behavior. +* - Auto-fixing is also unsafe when extra arguments are provided beyond the format string, as those expressions may have side effects. +* +* @private +* @param {ASTNode} node - call expression node +* @param {string} str - format string value +* @returns {boolean} boolean indicating whether the call can be auto-fixed +*/ +function canAutofix( node, str ) { + if ( node.arguments.length > 1 ) { + return false; + } + if ( str.indexOf( '%%' ) !== -1 ) { + return false; + } + return true; +} + +/** +* Rule for disallowing format calls that do not perform string interpolation. +* +* @param {Object} context - ESLint context +* @returns {Object} validators +*/ +function main( context ) { + var source = context.sourceCode; + + /** + * Validates a call expression. + * + * @private + * @param {ASTNode} node - node to examine + */ + function validate( node ) { + var str; + + // Note: member expression callees (e.g., `format.call( ... )` and `format.apply( ... )`) are intentionally skipped, as the callee is not an `Identifier` node... + if ( + node.callee === void 0 || + node.callee.type !== 'Identifier' || + !isFormat( source.getScope( node ), node.callee.name ) + ) { + return; + } + // Only check calls in which the format string is a string literal... + if ( + node.arguments.length === 0 || + node.arguments[ 0 ].type !== 'Literal' || + typeof node.arguments[ 0 ].value !== 'string' + ) { + return; + } + str = node.arguments[ 0 ].value; + if ( !hasPlaceholders( str ) ) { + context.report({ + 'node': node, + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'fix': ( canAutofix( node, str ) ) ? fix : null + }); + } + + /** + * Fixes the lint error by replacing the format call with the string literal. + * + * @private + * @param {Object} fixer - ESLint fixer + * @returns {Object} fix object + */ + function fix( fixer ) { + return fixer.replaceText( node, source.getText( node.arguments[ 0 ] ) ); + } + } + + return { + 'CallExpression': validate + }; +} + + +// MAIN // + +rule = { + 'meta': { + 'type': 'suggestion', + 'docs': { + 'description': 'disallow format calls that do not perform string interpolation' + }, + 'fixable': 'code', + 'schema': [] + }, + 'create': main +}; + + +// EXPORTS // + +module.exports = rule; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/package.json b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/package.json new file mode 100644 index 000000000000..073e6ab66dca --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/package.json @@ -0,0 +1,66 @@ +{ + "name": "@stdlib/_tools/eslint/rules/no-unnecessary-format", + "version": "0.0.0", + "description": "ESLint rule disallowing format calls that do not perform string interpolation.", + "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" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "tools", + "tool", + "eslint", + "lint", + "custom", + "rules", + "rule", + "plugin", + "format", + "interpolate", + "string", + "useless", + "unnecessary", + "placeholders" + ] +} diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js new file mode 100644 index 000000000000..203045515d54 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js @@ -0,0 +1,147 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 = []; +var test; + +// Static string... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format( \'hello world\' );' + ].join( '\n' ), + 'output': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = \'hello world\';' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + +// Nested in `new Error`... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'throw new Error( format( \'invalid argument.\' ) );' + ].join( '\n' ), + 'output': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'throw new Error( \'invalid argument.\' );' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + +// Escaped percent only (no conversion; no autofix due to `%%` unescaping)... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format( \'100%%\' );' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + +// Extra arguments (no autofix to avoid dropping expressions with side effects)... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format( \'hello\', x );' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + +// Alias other than `format`... +test = { + 'code': [ + 'var fmt = require( \'@stdlib/string/format\' );', + '', + 'var str = fmt( \'static text\' );' + ].join( '\n' ), + 'output': [ + 'var fmt = require( \'@stdlib/string/format\' );', + '', + 'var str = \'static text\';' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + +// Inner scope resolving to outer require... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'function foo() {', + ' return format( \'static text\' );', + '}' + ].join( '\n' ), + 'output': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'function foo() {', + ' return \'static text\';', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Unnecessary format call. No placeholders found in format string.', + 'type': 'CallExpression' + } + ] +}; +invalid.push( test ); + + +// EXPORTS // + +module.exports = invalid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js new file mode 100644 index 000000000000..e39c20a21b43 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js @@ -0,0 +1,57 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 unvalidated = []; +var test; + +test = { + 'code': [ + 'var x = 123;' + ].join( '\n' ) +}; +unvalidated.push( test ); + +test = { + 'code': [ + 'var str = format( \'hello\' );' + ].join( '\n' ) +}; +unvalidated.push( test ); + +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );' + ].join( '\n' ) +}; +unvalidated.push( test ); + +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format();' + ].join( '\n' ) +}; +unvalidated.push( test ); + + +// EXPORTS // + +module.exports = unvalidated; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js new file mode 100644 index 000000000000..7f25c605455a --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js @@ -0,0 +1,119 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 = []; +var test; + +// Format string with a placeholder... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format( \'%s\', \'foo\' );' + ].join( '\n' ) +}; +valid.push( test ); + +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'throw new Error( format( \'invalid argument. Must provide a string. Value: `%s`.\', \'foo\' ) );' + ].join( '\n' ) +}; +valid.push( test ); + +// Escaped percent sign with a placeholder... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format( \'%d%%\', 10 );' + ].join( '\n' ) +}; +valid.push( test ); + +// Non-literal format string... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = \'hello\';', + 'var out = format( str );' + ].join( '\n' ) +}; +valid.push( test ); + +// Calls via `#.call` and `#.apply`... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format.call( null, \'hello\' );' + ].join( '\n' ) +}; +valid.push( test ); + +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'var str = format.apply( null, [ \'hello\' ] );' + ].join( '\n' ) +}; +valid.push( test ); + +// Shadowed symbols... +test = { + 'code': [ + 'var format = require( \'@stdlib/string/format\' );', + '', + 'function foo( format ) {', + ' return format( \'hello\' );', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Unrelated symbols... +test = { + 'code': [ + 'var format = require( \'d3-format\' );', + '', + 'var str = format( \'hello\' );' + ].join( '\n' ) +}; +valid.push( test ); + +test = { + 'code': [ + 'function format( str ) {', + ' return str;', + '}', + '', + 'var str = format( \'hello\' );' + ].join( '\n' ) +}; +valid.push( test ); + + +// EXPORTS // + +module.exports = valid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/test.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/test.js new file mode 100644 index 000000000000..b77dcc36d61d --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/test.js @@ -0,0 +1,98 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 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 function positively validates format calls which perform string interpolation', function test( t ) { + var tester = new RuleTester({ + 'parserOptions': { + 'ecmaVersion': 2020 + } + }); + + try { + tester.run( 'no-unnecessary-format', rule, { + 'valid': valid, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the function negatively validates format calls which do not perform string interpolation', function test( t ) { + var tester = new RuleTester({ + 'parserOptions': { + 'ecmaVersion': 2020 + } + }); + + try { + tester.run( 'no-unnecessary-format', rule, { + 'valid': [], + 'invalid': invalid + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the function does not validate code not containing checkable format calls', function test( t ) { + var tester = new RuleTester({ + 'parserOptions': { + 'ecmaVersion': 2020 + } + }); + + try { + tester.run( 'no-unnecessary-format', rule, { + 'valid': unvalidated, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); From ddddb347e6db44ba1c02f32f7105163b6798362a Mon Sep 17 00:00:00 2001 From: Athan Date: Mon, 24 Aug 2026 23:31:40 -0700 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Athan Signed-off-by: Athan --- .../_tools/eslint/rules/no-unnecessary-format/lib/main.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js index 3954c479df12..070e2dc93627 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js @@ -20,6 +20,7 @@ // MODULES // +var isString = require( '@stdlib/assert/is-string' ).isPrimitive; var formatTokenize = require( '@stdlib/string/base/format-tokenize' ); @@ -80,7 +81,7 @@ function hasPlaceholders( str ) { tokens = formatTokenize( str ); for ( i = 0; i < tokens.length; i++ ) { - if ( typeof tokens[ i ] !== 'string' ) { + if ( !isString( tokens[ i ] ) ) { return true; } } @@ -140,7 +141,7 @@ function main( context ) { if ( node.arguments.length === 0 || node.arguments[ 0 ].type !== 'Literal' || - typeof node.arguments[ 0 ].value !== 'string' + !isString( node.arguments[ 0 ].value ) ) { return; } From 1e4178291cd737a928fd55253d7d1f9188644b46 Mon Sep 17 00:00:00 2001 From: Athan Date: Mon, 24 Aug 2026 23:33:16 -0700 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Athan Signed-off-by: Athan --- .../_tools/eslint/rules/no-unnecessary-format/lib/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js index 070e2dc93627..5ac150f61589 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/lib/main.js @@ -141,7 +141,7 @@ function main( context ) { if ( node.arguments.length === 0 || node.arguments[ 0 ].type !== 'Literal' || - !isString( node.arguments[ 0 ].value ) + !isString( node.arguments[ 0 ].value ) ) { return; } From e0bfde7ff0c53f5b162bc2b2e2c5dee06c4a7e26 Mon Sep 17 00:00:00 2001 From: Athan Date: Mon, 24 Aug 2026 23:34:42 -0700 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Athan Signed-off-by: Athan --- .../no-unnecessary-format/test/fixtures/invalid.js | 5 +---- .../test/fixtures/unvalidated.js | 4 +--- .../no-unnecessary-format/test/fixtures/valid.js | 12 +----------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js index 203045515d54..c0d1eab05b00 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js @@ -18,11 +18,8 @@ 'use strict'; -var invalid = []; -var test; - // Static string... -test = { +var test = { 'code': [ 'var format = require( \'@stdlib/string/format\' );', '', diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js index e39c20a21b43..8ea2ef0edcf1 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js @@ -18,10 +18,8 @@ 'use strict'; -var unvalidated = []; -var test; -test = { +var test = { 'code': [ 'var x = 123;' ].join( '\n' ) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js index 7f25c605455a..75aa3344aab1 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js @@ -10,19 +10,9 @@ * 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 = []; -var test; // Format string with a placeholder... -test = { +var test = { 'code': [ 'var format = require( \'@stdlib/string/format\' );', '', From 45f5668fdb7978a5c36f6c95c3e1366f1b5738a8 Mon Sep 17 00:00:00 2001 From: Athan Date: Mon, 24 Aug 2026 23:35:10 -0700 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Athan Signed-off-by: Athan --- .../eslint/rules/no-unnecessary-format/test/fixtures/invalid.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js index c0d1eab05b00..90153d0bd216 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/invalid.js @@ -18,6 +18,8 @@ 'use strict'; +var invalid = []; + // Static string... var test = { 'code': [ From a6834cee35f4059e5c48dcb3b63dd9637f2200da Mon Sep 17 00:00:00 2001 From: Athan Date: Mon, 24 Aug 2026 23:37:09 -0700 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Athan Signed-off-by: Athan --- .../no-unnecessary-format/test/fixtures/unvalidated.js | 1 + .../rules/no-unnecessary-format/test/fixtures/valid.js | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js index 8ea2ef0edcf1..168d7f04c106 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/unvalidated.js @@ -18,6 +18,7 @@ 'use strict'; +var unvalidated = []; var test = { 'code': [ diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js index 75aa3344aab1..7ff73f986358 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-format/test/fixtures/valid.js @@ -10,6 +10,15 @@ * 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 = []; // Format string with a placeholder... var test = {