fix(#2764): extend no-path-literal-in-assert to membership/substring checks (catches the #2728 Windows CI shape) (#2879)
* test(#2764): no-path-literal-in-assert must flag membership/substring checks over path-returners The equality-only visitor missed .includes/.indexOf/.startsWith/.endsWith over a path-returning receiver (directly or via a .map() hop) — these pass lint and fail on Windows. Add RuleTester invalid cases for each shape + valid cases for the suppressions (POSIX-normalized receiver, non-path receiver, no-slash arg). * fix(#2764): extend no-path-literal-in-assert to membership/substring checks over path-returners Add a third CallExpression shape: .includes/.indexOf/.startsWith/.endsWith/.match over a receiver that traces to a path-returning call (directly, or through ONE .map(f => path-…) hop — the #2728 shape that passed lint and failed Windows CI). Reuse isPathReturningCall/isPosixSlashStringLiteral/isPosixNormalizerCall; respect the normalizer + Windows-excluded suppressions. The rule is scoped to tests/**/*.test.cjs. * chore(#2764): changeset fragment * test+docs(#2764): add .match test coverage, Windows-excluded membership case, fix doc accuracy (review findings) - MAJOR: .match was supported but untested — add invalid (.match with slash) + valid (.match no-slash) RuleTester cases. - MINOR: Windows-excluded membership case was claimed by the matrix but absent — add a platform-guard valid case in membership form. - MINOR: .map() hop comment said 'ONE' but recursion allows nesting — reword to 'recursively'. - Add known-boundary (d) note for the membership/.match shape. - Fix a premature comment-close from a literal */ in the glob example. * chore(#2764): backfill changeset PR number (2879) --------- Co-authored-by: Test <test@example.com>
This commit is contained in:
@@ -179,6 +179,87 @@ describe('no-path-literal-in-assert invalid cases', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// ── #2764: membership / substring checks over a path-returning receiver ──
|
||||
// The equality-only visitor missed these; they pass lint and fail on Windows.
|
||||
|
||||
test('invalid (#2764): assert.ok(path.relative(R,f).includes("a/b"))', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(path.relative(ROOT, f).includes('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid (#2764): .map(f => path.relative(...)) hop then .includes (the #2728 shape)', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(arr.map(f => path.relative(ROOT, f)).includes('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid (#2764): path.join(...).indexOf("a/b") !== -1', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(path.join(a, b).indexOf('a/b') !== -1);`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid (#2764): path.resolve(...).startsWith("a/b")', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(path.resolve(x).startsWith('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid (#2764): path.resolve(...).endsWith("a/b")', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(path.resolve(x).endsWith('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid (#2764): path.resolve(...).match(/a\\/b/) — regex with a slash', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [],
|
||||
invalid: [
|
||||
{
|
||||
code: `assert.ok(path.resolve(x).match(/a\\/b/));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
errors: [{ messageId: 'pathLiteral' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── VALID cases (no violation expected) ─────────────────────────────────────
|
||||
@@ -337,6 +418,68 @@ describe('no-path-literal-in-assert valid cases', () => {
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
|
||||
// ── #2764: membership checks that must NOT trigger ──
|
||||
|
||||
test('valid (#2764): normalized receiver .includes("a/b") — POSIX normalizer suppresses', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [
|
||||
{
|
||||
code: `assert.ok(path.relative(ROOT, f).replace(/\\\\/g, '/').includes('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
},
|
||||
],
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('valid (#2764): non-path string receiver .includes("a/b")', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [
|
||||
{
|
||||
code: `assert.ok('plain string'.includes('a/b'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
},
|
||||
],
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('valid (#2764): path receiver .includes(arg-with-no-slash)', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [
|
||||
{
|
||||
code: `assert.ok(path.relative(ROOT, f).includes('foo'));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
},
|
||||
],
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('valid (#2764): path receiver .match(regex-with-no-slash)', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [
|
||||
{
|
||||
code: `assert.ok(path.resolve(x).match(/^home/));`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
},
|
||||
],
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('valid (#2764): membership inside a Windows-excluded platform guard', () => {
|
||||
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
|
||||
valid: [
|
||||
{
|
||||
code: `if (process.platform !== 'win32') { assert.ok(path.relative(ROOT, f).includes('a/b')); }`,
|
||||
filename: 'tests/foo.test.cjs',
|
||||
},
|
||||
],
|
||||
invalid: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── C1: isPosixNormalizerCall must require backslash-targeting regex ─────────
|
||||
|
||||
Reference in New Issue
Block a user