Files
msd-core/tests/no-crlf-fragile-split.rule.test.cjs

339 lines
11 KiB
JavaScript

'use strict';
/**
* no-crlf-fragile-split.rule.test.cjs
*
* RuleTester unit tests for the local/no-crlf-fragile-split ESLint rule.
*
* Rule covers three sub-patterns:
* G1 — .split('\n') on readFileSync-derived content (crlfFragileSplit)
* G2 — RegExp with bare \n on readFileSync-derived content (crlfFragileRegex)
* G3 — RegExp with bare \n containing markdown fence or frontmatter anchor
* (crlfFragileRegex) — caught even without direct data-flow
*
* NOTE: Fixture code strings must encode actual \n characters as \\n inside
* the JavaScript string literals used for RuleTester `code` fields, so that
* the ESLint parser receives the intended source text.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { RuleTester } = require('eslint');
const rule = require('../eslint-rules/no-crlf-fragile-split.cjs');
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'commonjs',
},
});
// ─── module shape ─────────────────────────────────────────────────────────────
describe('no-crlf-fragile-split rule module', () => {
test('exports meta and create', () => {
assert.strictEqual(typeof rule.meta, 'object');
assert.strictEqual(typeof rule.create, 'function');
assert.strictEqual(rule.meta.type, 'problem');
assert.ok(rule.meta.messages.crlfFragileSplit, 'crlfFragileSplit message must exist');
assert.ok(rule.meta.messages.crlfFragileRegex, 'crlfFragileRegex message must exist');
});
});
// ─── G1: INVALID cases ────────────────────────────────────────────────────────
describe('G1 — no-crlf-fragile-split: invalid (crlfFragileSplit)', () => {
test('G1-invalid: readFileSync(p).split("\\n") — direct chain', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// code that ESLint will parse: fs.readFileSync(p, 'utf8').split('\n')
code: "const lines = fs.readFileSync(p, 'utf8').split('\\n');",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileSplit' }],
},
],
});
});
test('G1-invalid: readFileSync(p).toString().split("\\n") — chained call', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
code: "const lines = readFileSync(p).toString().split('\\n');",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileSplit' }],
},
],
});
});
test('G1-invalid: content = readFileSync(...); content.split("\\n") — via variable', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
code: [
"const content = fs.readFileSync(filePath, 'utf8');",
"const lines = content.split('\\n');",
].join('\n'),
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileSplit' }],
},
],
});
});
test('G1-invalid: double-quoted "\\n" in split', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
code: 'const lines = fs.readFileSync(\'file.txt\', \'utf8\').split("\\n");',
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileSplit' }],
},
],
});
});
});
// ─── G1: VALID cases ──────────────────────────────────────────────────────────
describe('G1 — no-crlf-fragile-split: valid cases', () => {
test('G1-valid: .split(/\\r?\\n/) — correct regex', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
// /\r?\n/ in source — no bare \n in a string argument
code: "const lines = fs.readFileSync(p, 'utf8').split(/\\r?\\n/);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G1-valid: non-file content split — "\\n" on a plain string literal', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const lines = someString.split('\\n');",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G1-valid: non-file content split — "\\n" on variable not from readFileSync', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: [
"const content = 'hello\\\\nworld';",
"const lines = content.split('\\n');",
].join('\n'),
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G1-valid: .split("\\n") on a fetch/HTTP response (not readFileSync)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const lines = response.text.split('\\n');",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});
// ─── G2/G3: INVALID cases ─────────────────────────────────────────────────────
describe('G2/G3 — no-crlf-fragile-split: invalid (crlfFragileRegex)', () => {
test('G2-invalid: bare \\n in regex on readFileSync content via .match()', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// /foo\nbar/ — regex with bare \n; .match() on readFileSync result
code: "const m = fs.readFileSync(p, 'utf8').match(/foo\\nbar/);",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('G2-invalid: bare \\n in regex on readFileSync content via .test()', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// /hello\nworld/.test(readFileSync(...))
code: "const ok = /hello\\nworld/.test(fs.readFileSync(p, 'utf8'));",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('G2-invalid: bare \\n in regex on readFileSync content via .replace()', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
code: "const out = fs.readFileSync(p, 'utf8').replace(/foo\\nbar/, 'x');",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('G3-invalid: markdown fence regex with bare \\n (```bash\\n)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// content.match(/```bash\nsome/) — fence regex with bare \n
code: "const m = content.match(/```bash\\nsome/);",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('G3-invalid: frontmatter anchor regex with bare \\n (/^---\\n/)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// /^---\ntitle/.test(content) — frontmatter with bare \n
code: "const hasFM = /^---\\ntitle/.test(content);",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
});
// ─── G2/G3: VALID cases ───────────────────────────────────────────────────────
describe('G2/G3 — no-crlf-fragile-split: valid cases', () => {
test('G2-valid: regex with \\r?\\n (already CRLF-safe) on readFileSync content', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
// /foo\r?\nbar/ — has \r?\n so it's safe
code: "const m = fs.readFileSync(p, 'utf8').match(/foo\\r?\\nbar/);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G2-valid: regex with bare \\n but used on a non-file string (ok per known boundaries)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
// /hello\nworld/.test(someRuntimeString) — not file content
code: "const ok = /hello\\nworld/.test(someRuntimeString);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G3-valid: markdown fence regex but with \\r?\\n (already CRLF-safe)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const m = content.match(/```bash\\r?\\nsome/);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('G3-valid: frontmatter regex with \\r\\n (explicitly CRLF-safe)', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const hasFM = /^---\\r\\ntitle/.test(content);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
// C3 per-occurrence classification cases
test('C3-valid: /\\r?\\n/ — single safe occurrence, not flagged', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const m = fs.readFileSync(p, 'utf8').match(/\\r?\\n/);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('C3-invalid: regex with both safe \\r?\\n AND a separate bare \\n — flagged', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// /\r?\nfoo|\nbar/ — the second \n (after |) is bare and fragile
code: "const m = fs.readFileSync(p, 'utf8').match(/\\r?\\nfoo|\\nbar/);",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('C3-invalid: [^\\n] — \\n in class without \\r is fragile', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [],
invalid: [
{
// /[^\n]+/ — class has \n but no \r
code: "const m = fs.readFileSync(p, 'utf8').match(/[^\\n]+/);",
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'crlfFragileRegex' }],
},
],
});
});
test('C3-valid: [^\\r\\n] — \\n in class with \\r is safe', () => {
ruleTester.run('no-crlf-fragile-split', rule, {
valid: [
{
code: "const m = fs.readFileSync(p, 'utf8').match(/[^\\r\\n]+/);",
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});