382 lines
13 KiB
JavaScript
382 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* platform-guard.unit.test.cjs
|
|
*
|
|
* Unit tests for eslint-rules/lib/platform-guard.cjs.
|
|
* Verifies classifyPlatformTest and isWindowsExcludedNode shapes
|
|
* using espree to parse code snippets into ASTs.
|
|
*/
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const espree = require('espree');
|
|
const { Linter } = require('eslint');
|
|
|
|
const { classifyPlatformTest, isWindowsExcludedNode } = require('../eslint-rules/lib/platform-guard.cjs');
|
|
|
|
const PARSE_OPTIONS = {
|
|
ecmaVersion: 2022,
|
|
sourceType: 'script',
|
|
range: true,
|
|
loc: true,
|
|
tokens: true,
|
|
comment: true,
|
|
};
|
|
|
|
function parse(code) {
|
|
return espree.parse(code, PARSE_OPTIONS);
|
|
}
|
|
|
|
// ─── classifyPlatformTest ─────────────────────────────────────────────────────
|
|
|
|
describe('classifyPlatformTest', () => {
|
|
test('process.platform === "win32" → windows', () => {
|
|
const ast = parse(`process.platform === 'win32'`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('"win32" === process.platform (reversed) → windows', () => {
|
|
const ast = parse(`'win32' === process.platform`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('process.platform !== "win32" → not-windows', () => {
|
|
const ast = parse(`process.platform !== 'win32'`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'not-windows');
|
|
});
|
|
|
|
test('os.platform() === "win32" → windows', () => {
|
|
const ast = parse(`os.platform() === 'win32'`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('os.platform() !== "win32" → not-windows', () => {
|
|
const ast = parse(`os.platform() !== 'win32'`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'not-windows');
|
|
});
|
|
|
|
test('isWindows identifier → windows', () => {
|
|
const ast = parse(`isWindows`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('IS_WINDOWS identifier → windows', () => {
|
|
const ast = parse(`IS_WINDOWS`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('isWin identifier → windows', () => {
|
|
const ast = parse(`isWin`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('onWindows identifier → windows', () => {
|
|
const ast = parse(`onWindows`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'windows');
|
|
});
|
|
|
|
test('!isWindows → not-windows', () => {
|
|
const ast = parse(`!isWindows`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), 'not-windows');
|
|
});
|
|
|
|
test('unrelated expression → null', () => {
|
|
const ast = parse(`x === 'linux'`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), null);
|
|
});
|
|
|
|
test('bare identifier "result" → null', () => {
|
|
const ast = parse(`result`);
|
|
const node = ast.body[0].expression;
|
|
assert.strictEqual(classifyPlatformTest(node), null);
|
|
});
|
|
});
|
|
|
|
// ─── isWindowsExcludedNode via Linter ────────────────────────────────────────
|
|
// We use the Linter + a custom rule to get real sourceCode with ancestors.
|
|
|
|
/**
|
|
* Build a mini rule that collects data about CallExpression nodes named
|
|
* "assert.equal" and checks isWindowsExcludedNode on them.
|
|
*/
|
|
function buildCollectorRule() {
|
|
return {
|
|
create(context) {
|
|
const sourceCode = context.sourceCode;
|
|
const results = [];
|
|
return {
|
|
CallExpression(node) {
|
|
if (
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.name === 'assert' &&
|
|
node.callee.property.name === 'equal'
|
|
) {
|
|
results.push(isWindowsExcludedNode(node, sourceCode));
|
|
}
|
|
},
|
|
'Program:exit'() {
|
|
context.report({ node: sourceCode.ast, messageId: 'result', data: { results: JSON.stringify(results) } });
|
|
},
|
|
};
|
|
},
|
|
meta: { type: 'suggestion', schema: [], messages: { result: '{{results}}' } },
|
|
};
|
|
}
|
|
|
|
function runCollector(code) {
|
|
const linter = new Linter({ configType: 'flat' });
|
|
const messages = linter.verify(
|
|
code,
|
|
[{ plugins: { t: { rules: { collector: buildCollectorRule() } } }, rules: { 't/collector': 'warn' }, languageOptions: { ecmaVersion: 2022, sourceType: 'script' } }],
|
|
{ filename: 'tests/x.test.cjs' }
|
|
);
|
|
// The rule emits exactly one message (Program:exit) with results as JSON
|
|
const msg = messages.find(m => m.ruleId === 't/collector');
|
|
if (!msg) return [];
|
|
return JSON.parse(msg.message);
|
|
}
|
|
|
|
describe('isWindowsExcludedNode — if (!windows) { assert } shape', () => {
|
|
test('assert inside if (process.platform !== "win32") block → excluded=true', () => {
|
|
const code = `
|
|
if (process.platform !== 'win32') {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('assert inside else of if (process.platform === "win32") block → excluded=true', () => {
|
|
const code = `
|
|
if (process.platform === 'win32') {
|
|
doWindows();
|
|
} else {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('assert NOT inside any guard → excluded=false', () => {
|
|
const code = `assert.equal(path.join(a, b), '/x/y');`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false]);
|
|
});
|
|
});
|
|
|
|
describe('isWindowsExcludedNode — early-return guard shape', () => {
|
|
test('if (process.platform === "win32") return; assert.equal(...) → excluded=true', () => {
|
|
const code = `
|
|
function test() {
|
|
if (process.platform === 'win32') return;
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('if (process.platform === "win32") return t.skip(); assert.equal → excluded=true', () => {
|
|
const code = `
|
|
function test(t) {
|
|
if (process.platform === 'win32') return t.skip('no windows');
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('guard appears AFTER the assert → excluded=false', () => {
|
|
const code = `
|
|
function test() {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
if (process.platform === 'win32') return;
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false]);
|
|
});
|
|
});
|
|
|
|
describe('isWindowsExcludedNode — hoisted isWindows guard shape', () => {
|
|
test('const isWindows = process.platform === "win32"; if (!isWindows) assert.equal → excluded=true', () => {
|
|
const code = `
|
|
const isWindows = process.platform === 'win32';
|
|
if (!isWindows) assert.equal(path.join(a, b), '/x/y');
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('const isWindows = …; if (isWindows) {} else { assert.equal } → excluded=true', () => {
|
|
const code = `
|
|
const isWindows = process.platform === 'win32';
|
|
if (isWindows) { doWindowsThing(); } else { assert.equal(path.join(a,b), '/x/y'); }
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
});
|
|
|
|
describe('isWindowsExcludedNode — arbitrary-named hoisted boolean (real hoisting)', () => {
|
|
test('const winFlag = process.platform === "win32"; if (!winFlag) assert.equal → excluded=true', () => {
|
|
const code = `
|
|
function test() {
|
|
const winFlag = process.platform === 'win32';
|
|
if (!winFlag) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('const winFlag = process.platform === "win32"; if (winFlag) return; assert.equal → excluded=true', () => {
|
|
const code = `
|
|
function test() {
|
|
const winFlag = process.platform === 'win32';
|
|
if (winFlag) return;
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('unrelated variable used as guard is NOT excluded', () => {
|
|
const code = `
|
|
function test() {
|
|
const debugMode = true;
|
|
if (!debugMode) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false]);
|
|
});
|
|
|
|
test('winFlag declared AFTER the assert is NOT a guard → excluded=false', () => {
|
|
const code = `
|
|
function test() {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
const winFlag = process.platform === 'win32';
|
|
if (!winFlag) { doSomething(); }
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false]);
|
|
});
|
|
});
|
|
|
|
// ─── C2: early-return guard must climb ancestor blocks ────────────────────────
|
|
|
|
describe('C2 — early-return guard climbs ancestor blocks', () => {
|
|
test('C2: early-return in outer block before nested if-block → excluded=true', () => {
|
|
// The guard is in the function body; assert.equal is inside a nested if-block.
|
|
// Before the fix, _hasEarlyWindowsReturnBefore only checked the innermost block.
|
|
const code = `
|
|
function test() {
|
|
if (process.platform === 'win32') return;
|
|
if (cond) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true]);
|
|
});
|
|
|
|
test('C2: no early-return at all → excluded=false', () => {
|
|
const code = `
|
|
function test() {
|
|
if (cond) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false]);
|
|
});
|
|
});
|
|
|
|
// ─── C3: binding-aware identifier classification ──────────────────────────────
|
|
|
|
describe('C3 — binding-aware identifier classification', () => {
|
|
test('C3 case 1: const isWindows = false; if (!isWindows) assert.equal → excluded=false (FLAGGED)', () => {
|
|
// isWindows is in WINDOWS_BOOL_NAMES but its binding is `false`, not a platform test.
|
|
const code = `
|
|
function test() {
|
|
const isWindows = false;
|
|
if (!isWindows) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false], 'const isWindows = false should not be treated as a platform guard');
|
|
});
|
|
|
|
test('C3 case 2: let w = platform===win32; w = false; if (!w) assert.equal → excluded=false (FLAGGED)', () => {
|
|
// w starts as a platform test but is reassigned — should NOT be trusted.
|
|
const code = `
|
|
function test() {
|
|
let w = process.platform === 'win32';
|
|
w = false;
|
|
if (!w) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false], 'reassigned variable should not be treated as a platform guard');
|
|
});
|
|
|
|
test('C3 case 3: inner shadow const w = false wins over outer const w = platform test → excluded=false (FLAGGED)', () => {
|
|
// The inner const w = false shadows the outer const w = platform test.
|
|
const code = `
|
|
function test() {
|
|
const w = process.platform === 'win32';
|
|
{
|
|
const w = false;
|
|
if (!w) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [false], 'inner shadow (w=false) should win over outer platform test');
|
|
});
|
|
|
|
test('C3 case 4: const w = platform test; if (!w) assert.equal → excluded=true (genuine guard)', () => {
|
|
// The canonical case — should still work.
|
|
const code = `
|
|
function test() {
|
|
const w = process.platform === 'win32';
|
|
if (!w) {
|
|
assert.equal(path.join(a, b), '/x/y');
|
|
}
|
|
}
|
|
`;
|
|
const results = runCollector(code);
|
|
assert.deepStrictEqual(results, [true], 'genuine platform guard should suppress the report');
|
|
});
|
|
});
|