biome nursery/useValidTestTitle
labelled safe
Analysis Nemotron 3 Super
The 'useValidTestTitle' rule flags test block calls where the title argument is missing, non-string/literal, or has leading/trailing whitespace.
Fix: Replaces the invalid title argument with a trimmed string literal if it is a string or template literal; otherwise removes the argument entirely, preserving all other arguments and formatting.
10 hazards
- Rewriting a non-literal title (e.g., a variable) to a string literal changes runtime behavior if the title was dynamically computed.
- Removing a non-literal title argument shifts subsequent arguments left, potentially causing a function to receive an unexpected value (e.g., a callback becoming the title).
- If the title is a template literal with side effects (e.g., `${fn()}`), removing it eliminates those side effects.
- Trimming whitespace in a title that is intentionally used for visual alignment or formatting in test output alters test reporting aesthetics.
- If the title is part of a larger expression (e.g., concatenation), replacing it breaks the expression and may cause syntax errors.
- When the title is omitted and the rule inserts a blank string, some test frameworks may treat blank titles differently than missing titles (though rare).
- Comments attached to the title argument (e.g., /* title */ " foo" ) may be lost during replacement if the tool does not preserve them.
- In nested test blocks, altering a parent suite's title may affect how child tests are grouped or reported in hierarchies.
- If the title is a tagged template literal, replacing it with a plain string loses the tag's semantic meaning and any custom processing.
- Changing a title from a string containing only whitespace to an empty string may affect tests that rely on title length for conditional logic (highly unconventional but possible).
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Comment attached to removed title causes misalignment | comments attached to the removed or moved code |
| r1-p002 | fired, no fix | Removing non-literal title shifts callback into title position | Removing a non-literal title argument shifts subsequent arguments left, potentially causing a function to receive an unexpected value (e.g., a callback becoming the title). |
| r1-p003 | fired, no fix | Title as part of concatenation breaks expression | If the title is part of a larger expression (e.g., concatenation), replacing it breaks the expression and may cause syntax errors. |
| r1-p004 | fired, no fix | Side effect in title runs zero times after removal | order of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never) |
| r1-p005 | silent prediction off | Template literal side effects eliminated | If the title is a template literal with side effects (e.g., ${fn()}), removing it eliminates those side effects. |
| r1-p006 | fix ok | Comment on title lost during replacement | Comments attached to the title argument (e.g., /* title */ " foo" ) may be lost during replacement if the tool does not preserve them. |
| r1-p007 | fired, no fix | Exception in removed title argument is silenced | exceptions thrown inside the code the fix removes or rewrites |
| r1-p008 | silent prediction off | Tagged template loses semantic meaning | If the title is a tagged template literal, replacing it with a plain string loses the tag's semantic meaning and any custom processing. |
| r2-p002 | fired, no fix | visual alignment via whitespace | Trimming whitespace in a title that is intentionally used for visual alignment or formatting in test output alters test reporting aesthetics. |
| r2-p003 | fix ok | strict vs sloppy mode | strict vs sloppy mode semantics |
| r2-p004 | fired, no fix | blank title insertion | When the title is omitted and the rule inserts a blank string, some test frameworks may treat blank titles differently than missing titles (though rare). |
| r2-p005 | fired, no fix | whitespace-only title length | Changing a title from a string containing only whitespace to an empty string may affect tests that rely on title length for conditional logic (highly unconventional but possible). |
| r2-p006 | fix ok | nested suite grouping | In nested test blocks, altering a parent suite's title may affect how child tests are grouped or reported in hierarchies. |
| r2-p008 | fix ok | this binding arrow vs function | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
Sources
r1-p001 · Comment attached to removed title causes misalignment
1// Comment above
2function test(title, cb) {
3 return cb ? cb(title) : title;
4}
5let result = '';
6result += test(/* title */ 123, x => { result += x; });
7console.log(result);
undefined
r1-p002 · Removing non-literal title shifts callback into title position
1function test(a, b) {
2 console.log(typeof a + ',' + typeof b);
3}
4test(() => { console.log('callback ran'); }, 'should be second');
function,string
r1-p003 · Title as part of concatenation breaks expression
1function test(title, fn) {
2 console.log(title.length);
3 fn();
4}
5test('hello' + ' world', () => { console.log('ok'); });
11 ok
r1-p004 · Side effect in title runs zero times after removal
1let count = 0;
2function getTitle() {
3 count++;
4 return 'test' + count;
5}
6function test(title, fn) {
7 console.log('title:', title);
8 fn();
9}
10test(getTitle(), () => { console.log('fn ran'); });
11console.log('count:', count);
title: test1 fn ran count: 1
r1-p005 · Template literal side effects eliminated
1let logs = [];
2function side() {
3 logs.push('side');
4 return 'ok';
5}
6function test(title, fn) {
7 console.log('got:', title);
8 fn();
9}
10test(`${side()}`, () => { logs.push('fn'); });
11console.log(logs.join(','));
got: ok side,fn
r1-p006 · Comment on title lost during replacement
1function test(title, value) {
2 console.log(title.trim() + ':' + value);
3}
4test(/* title */ ' hello ', 42);
--- orig.js 2026-10-17 16:51:41.440036318 -0400 +++ fixed.js 2026-10-17 16:51:41.715041260 -0400 @@ -1,4 +1,4 @@ function test(title, value) { console.log(title.trim() + ':' + value); } -test(/* title */ ' hello ', 42); +test(/* title */ 'hello', 42);
hello:42
r1-p007 · Exception in removed title argument is silenced
1function test(title, handler) {
2 try {
3 handler(title);
4 } catch (e) {
5 console.log('caught:', e.message);
6 }
7}
8function bad() {
9 throw new Error('boom');
10}
11test(bad(), msg => { console.log('handler:', msg); });
r1-p008 · Tagged template loses semantic meaning
1function html(strings, ...values) {
2 return strings.raw[0] + values.map((v, i) => String(values[i]) + strings.raw[i+1]).join('');
3}
4function test(title, log) {
5 log(title);
6}
7test(html`<b>bold</b>`, msg => console.log('rendered:', msg));
rendered: <b>bold</b>
r2-p002 · visual alignment via whitespace
1const levels = ['unit', 'integration', 'e2e'];
2levels.forEach(level => {
3 describe(` ${level.toUpperCase()} `, () => {
4 // Intentionally padded for column-aligned test output
5 console.log(`[${level}] suite entered`);
6 it(` ${level}-test `, () => {
7 console.log(` -> ${level} test ran`);
8 });
9 });
10});
r2-p003 · strict vs sloppy mode
1'use strict';
2(function() {
3 'use sloppy';
4 test(' strict? ', function() {
5 // In sloppy func, `this` is global; in strict, undefined
6 console.log(this === global ? 'global' : this === undefined ? 'undefined' : 'other');
7 });
8})();
--- orig.js 2026-10-17 16:51:51.051208285 -0400 +++ fixed.js 2026-10-17 16:51:51.317213021 -0400 @@ -1,7 +1,7 @@ 'use strict'; (function() { 'use sloppy'; - test(' strict? ', function() { + test('strict?', function() { // In sloppy func, `this` is global; in strict, undefined console.log(this === global ? 'global' : this === undefined ? 'undefined' : 'other'); });
r2-p004 · blank title insertion
1function logTitle(t) {
2 console.log(t.length === 0 && t === '' ? 'blank' : t.length > 0 ? 'nonblank' : 'missing');
3}
4describe(undefined, () => {}); // <-- TRIGGER: missing title → becomes ''
5it(undefined, () => {}); // <-- TRIGGER: missing title → becomes ''
r2-p005 · whitespace-only title length
1[' ', '\t\n\r '].forEach(title => {
2 const len = title.length;
3 test(title, () => {
4 console.log(len === 0 ? 'zero' : len > 0 && len <= 2 ? 'short' : 'long');
5 });
6});
r2-p006 · nested suite grouping
1describe(' parent ', () => {
2 console.log('suite:', ' parent '.trim());
3 it('child', () => {
4 console.log(' → test under:', ' parent '.trim());
5 });
6});
--- orig.js 2026-10-17 16:51:52.031225734 -0400 +++ fixed.js 2026-10-17 16:51:52.330231053 -0400 @@ -1,4 +1,4 @@ -describe(' parent ', () => { +describe('parent', () => { console.log('suite:', ' parent '.trim()); it('child', () => { console.log(' → test under:', ' parent '.trim());
r2-p008 · this binding arrow vs function
1const obj = {
2 value: 42,
3 getValue: function() { return this.value; }
4};
5test(' this test ', () => {
6 console.log('arrow:', obj.getValue());
7 const fn = obj.getValue;
8 console.log('extracted:', fn()); // undefined in strict
9 console.log('bound:', fn.call(obj)); // 42
10});
--- orig.js 2026-10-17 16:51:52.498234042 -0400 +++ fixed.js 2026-10-17 16:51:52.758238667 -0400 @@ -2,7 +2,7 @@ value: 42, getValue: function() { return this.value; } }; -test(' this test ', () => { +test('this test', () => { console.log('arrow:', obj.getValue()); const fn = obj.getValue; console.log('extracted:', fn()); // undefined in strict