mirror of
https://git.proxmox.com/git/pve-eslint
synced 2025-10-05 02:51:13 +00:00
574 lines
22 KiB
JavaScript
574 lines
22 KiB
JavaScript
/**
|
|
* @fileoverview Tests for CodePathAnalyzer.
|
|
* @author Toru Nagashima
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
const assert = require("assert"),
|
|
fs = require("fs"),
|
|
path = require("path"),
|
|
{ Linter } = require("../../../../lib/linter"),
|
|
EventGeneratorTester = require("../../../../tools/internal-testers/event-generator-tester"),
|
|
createEmitter = require("../../../../lib/linter/safe-emitter"),
|
|
debug = require("../../../../lib/linter/code-path-analysis/debug-helpers"),
|
|
CodePath = require("../../../../lib/linter/code-path-analysis/code-path"),
|
|
CodePathAnalyzer = require("../../../../lib/linter/code-path-analysis/code-path-analyzer"),
|
|
CodePathSegment = require("../../../../lib/linter/code-path-analysis/code-path-segment"),
|
|
NodeEventGenerator = require("../../../../lib/linter/node-event-generator");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helpers
|
|
//------------------------------------------------------------------------------
|
|
|
|
const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu;
|
|
const lineEndingPattern = /\r?\n/gu;
|
|
const linter = new Linter();
|
|
|
|
/**
|
|
* Extracts the content of `/*expected` comments from a given source code.
|
|
* It's expected DOT arrows.
|
|
* @param {string} source A source code text.
|
|
* @returns {string[]} DOT arrows.
|
|
*/
|
|
function getExpectedDotArrows(source) {
|
|
expectedPattern.lastIndex = 0;
|
|
|
|
const retv = [];
|
|
let m;
|
|
|
|
while ((m = expectedPattern.exec(source)) !== null) {
|
|
retv.push(m[1].replace(lineEndingPattern, "\n"));
|
|
}
|
|
|
|
return retv;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Tests
|
|
//------------------------------------------------------------------------------
|
|
|
|
describe("CodePathAnalyzer", () => {
|
|
EventGeneratorTester.testEventGeneratorInterface(
|
|
new CodePathAnalyzer(new NodeEventGenerator(createEmitter()))
|
|
);
|
|
|
|
describe("interface of code paths", () => {
|
|
let actual = [];
|
|
|
|
beforeEach(() => {
|
|
actual = [];
|
|
linter.defineRule("test", () => ({
|
|
onCodePathStart(codePath) {
|
|
actual.push(codePath);
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"function foo(a) { if (a) return 0; else throw new Error(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
});
|
|
|
|
it("should have `id` as unique string", () => {
|
|
assert(typeof actual[0].id === "string");
|
|
assert(typeof actual[1].id === "string");
|
|
assert(actual[0].id !== actual[1].id);
|
|
});
|
|
|
|
it("should have `upper` as CodePath", () => {
|
|
assert(actual[0].upper === null);
|
|
assert(actual[1].upper === actual[0]);
|
|
});
|
|
|
|
it("should have `childCodePaths` as CodePath[]", () => {
|
|
assert(Array.isArray(actual[0].childCodePaths));
|
|
assert(Array.isArray(actual[1].childCodePaths));
|
|
assert(actual[0].childCodePaths.length === 1);
|
|
assert(actual[1].childCodePaths.length === 0);
|
|
assert(actual[0].childCodePaths[0] === actual[1]);
|
|
});
|
|
|
|
it("should have `initialSegment` as CodePathSegment", () => {
|
|
assert(actual[0].initialSegment instanceof CodePathSegment);
|
|
assert(actual[1].initialSegment instanceof CodePathSegment);
|
|
assert(actual[0].initialSegment.prevSegments.length === 0);
|
|
assert(actual[1].initialSegment.prevSegments.length === 0);
|
|
});
|
|
|
|
it("should have `finalSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].finalSegments));
|
|
assert(Array.isArray(actual[1].finalSegments));
|
|
assert(actual[0].finalSegments.length === 1);
|
|
assert(actual[1].finalSegments.length === 2);
|
|
assert(actual[0].finalSegments[0].nextSegments.length === 0);
|
|
assert(actual[1].finalSegments[0].nextSegments.length === 0);
|
|
assert(actual[1].finalSegments[1].nextSegments.length === 0);
|
|
|
|
// finalSegments should include returnedSegments and thrownSegments.
|
|
assert(actual[0].finalSegments[0] === actual[0].returnedSegments[0]);
|
|
assert(actual[1].finalSegments[0] === actual[1].returnedSegments[0]);
|
|
assert(actual[1].finalSegments[1] === actual[1].thrownSegments[0]);
|
|
});
|
|
|
|
it("should have `returnedSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].returnedSegments));
|
|
assert(Array.isArray(actual[1].returnedSegments));
|
|
assert(actual[0].returnedSegments.length === 1);
|
|
assert(actual[1].returnedSegments.length === 1);
|
|
assert(actual[0].returnedSegments[0] instanceof CodePathSegment);
|
|
assert(actual[1].returnedSegments[0] instanceof CodePathSegment);
|
|
});
|
|
|
|
it("should have `thrownSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].thrownSegments));
|
|
assert(Array.isArray(actual[1].thrownSegments));
|
|
assert(actual[0].thrownSegments.length === 0);
|
|
assert(actual[1].thrownSegments.length === 1);
|
|
assert(actual[1].thrownSegments[0] instanceof CodePathSegment);
|
|
});
|
|
|
|
it("should have `currentSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].currentSegments));
|
|
assert(Array.isArray(actual[1].currentSegments));
|
|
assert(actual[0].currentSegments.length === 0);
|
|
assert(actual[1].currentSegments.length === 0);
|
|
|
|
// there is the current segment in progress.
|
|
linter.defineRule("test", () => {
|
|
let codePath = null;
|
|
|
|
return {
|
|
onCodePathStart(cp) {
|
|
codePath = cp;
|
|
},
|
|
ReturnStatement() {
|
|
assert(codePath.currentSegments.length === 1);
|
|
assert(codePath.currentSegments[0] instanceof CodePathSegment);
|
|
},
|
|
ThrowStatement() {
|
|
assert(codePath.currentSegments.length === 1);
|
|
assert(codePath.currentSegments[0] instanceof CodePathSegment);
|
|
}
|
|
};
|
|
});
|
|
linter.verify(
|
|
"function foo(a) { if (a) return 0; else throw new Error(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("interface of code path segments", () => {
|
|
let actual = [];
|
|
|
|
beforeEach(() => {
|
|
actual = [];
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentStart(segment) {
|
|
actual.push(segment);
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"function foo(a) { if (a) return 0; else throw new Error(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
});
|
|
|
|
it("should have `id` as unique string", () => {
|
|
assert(typeof actual[0].id === "string");
|
|
assert(typeof actual[1].id === "string");
|
|
assert(typeof actual[2].id === "string");
|
|
assert(typeof actual[3].id === "string");
|
|
assert(actual[0].id !== actual[1].id);
|
|
assert(actual[0].id !== actual[2].id);
|
|
assert(actual[0].id !== actual[3].id);
|
|
assert(actual[1].id !== actual[2].id);
|
|
assert(actual[1].id !== actual[3].id);
|
|
assert(actual[2].id !== actual[3].id);
|
|
});
|
|
|
|
it("should have `nextSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].nextSegments));
|
|
assert(Array.isArray(actual[1].nextSegments));
|
|
assert(Array.isArray(actual[2].nextSegments));
|
|
assert(Array.isArray(actual[3].nextSegments));
|
|
assert(actual[0].nextSegments.length === 0);
|
|
assert(actual[1].nextSegments.length === 2);
|
|
assert(actual[2].nextSegments.length === 0);
|
|
assert(actual[3].nextSegments.length === 0);
|
|
assert(actual[1].nextSegments[0] === actual[2]);
|
|
assert(actual[1].nextSegments[1] === actual[3]);
|
|
});
|
|
|
|
it("should have `allNextSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].allNextSegments));
|
|
assert(Array.isArray(actual[1].allNextSegments));
|
|
assert(Array.isArray(actual[2].allNextSegments));
|
|
assert(Array.isArray(actual[3].allNextSegments));
|
|
assert(actual[0].allNextSegments.length === 0);
|
|
assert(actual[1].allNextSegments.length === 2);
|
|
assert(actual[2].allNextSegments.length === 1);
|
|
assert(actual[3].allNextSegments.length === 1);
|
|
assert(actual[2].allNextSegments[0].reachable === false);
|
|
assert(actual[3].allNextSegments[0].reachable === false);
|
|
});
|
|
|
|
it("should have `prevSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].prevSegments));
|
|
assert(Array.isArray(actual[1].prevSegments));
|
|
assert(Array.isArray(actual[2].prevSegments));
|
|
assert(Array.isArray(actual[3].prevSegments));
|
|
assert(actual[0].prevSegments.length === 0);
|
|
assert(actual[1].prevSegments.length === 0);
|
|
assert(actual[2].prevSegments.length === 1);
|
|
assert(actual[3].prevSegments.length === 1);
|
|
assert(actual[2].prevSegments[0] === actual[1]);
|
|
assert(actual[3].prevSegments[0] === actual[1]);
|
|
});
|
|
|
|
it("should have `allPrevSegments` as CodePathSegment[]", () => {
|
|
assert(Array.isArray(actual[0].allPrevSegments));
|
|
assert(Array.isArray(actual[1].allPrevSegments));
|
|
assert(Array.isArray(actual[2].allPrevSegments));
|
|
assert(Array.isArray(actual[3].allPrevSegments));
|
|
assert(actual[0].allPrevSegments.length === 0);
|
|
assert(actual[1].allPrevSegments.length === 0);
|
|
assert(actual[2].allPrevSegments.length === 1);
|
|
assert(actual[3].allPrevSegments.length === 1);
|
|
});
|
|
|
|
it("should have `reachable` as boolean", () => {
|
|
assert(actual[0].reachable === true);
|
|
assert(actual[1].reachable === true);
|
|
assert(actual[2].reachable === true);
|
|
assert(actual[3].reachable === true);
|
|
});
|
|
});
|
|
|
|
describe("onCodePathStart", () => {
|
|
it("should be fired at the head of programs/functions", () => {
|
|
let count = 0;
|
|
let lastCodePathNodeType = null;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathStart(cp, node) {
|
|
count += 1;
|
|
lastCodePathNodeType = node.type;
|
|
|
|
assert(cp instanceof CodePath);
|
|
if (count === 1) {
|
|
assert(node.type === "Program");
|
|
} else if (count === 2) {
|
|
assert(node.type === "FunctionDeclaration");
|
|
} else if (count === 3) {
|
|
assert(node.type === "FunctionExpression");
|
|
} else if (count === 4) {
|
|
assert(node.type === "ArrowFunctionExpression");
|
|
}
|
|
},
|
|
Program() {
|
|
assert(lastCodePathNodeType === "Program");
|
|
},
|
|
FunctionDeclaration() {
|
|
assert(lastCodePathNodeType === "FunctionDeclaration");
|
|
},
|
|
FunctionExpression() {
|
|
assert(lastCodePathNodeType === "FunctionExpression");
|
|
},
|
|
ArrowFunctionExpression() {
|
|
assert(lastCodePathNodeType === "ArrowFunctionExpression");
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"foo(); function foo() {} var foo = function() {}; var foo = () => {};",
|
|
{ rules: { test: 2 }, env: { es6: true } }
|
|
);
|
|
|
|
assert(count === 4);
|
|
});
|
|
});
|
|
|
|
describe("onCodePathEnd", () => {
|
|
it("should be fired at the end of programs/functions", () => {
|
|
let count = 0;
|
|
let lastNodeType = null;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathEnd(cp, node) {
|
|
count += 1;
|
|
|
|
assert(cp instanceof CodePath);
|
|
if (count === 4) {
|
|
assert(node.type === "Program");
|
|
} else if (count === 1) {
|
|
assert(node.type === "FunctionDeclaration");
|
|
} else if (count === 2) {
|
|
assert(node.type === "FunctionExpression");
|
|
} else if (count === 3) {
|
|
assert(node.type === "ArrowFunctionExpression");
|
|
}
|
|
assert(node.type === lastNodeType);
|
|
},
|
|
"Program:exit"() {
|
|
lastNodeType = "Program";
|
|
},
|
|
"FunctionDeclaration:exit"() {
|
|
lastNodeType = "FunctionDeclaration";
|
|
},
|
|
"FunctionExpression:exit"() {
|
|
lastNodeType = "FunctionExpression";
|
|
},
|
|
"ArrowFunctionExpression:exit"() {
|
|
lastNodeType = "ArrowFunctionExpression";
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"foo(); function foo() {} var foo = function() {}; var foo = () => {};",
|
|
{ rules: { test: 2 }, env: { es6: true } }
|
|
);
|
|
|
|
assert(count === 4);
|
|
});
|
|
});
|
|
|
|
describe("onCodePathSegmentStart", () => {
|
|
it("should be fired at the head of programs/functions for the initial segment", () => {
|
|
let count = 0;
|
|
let lastCodePathNodeType = null;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentStart(segment, node) {
|
|
count += 1;
|
|
lastCodePathNodeType = node.type;
|
|
|
|
assert(segment instanceof CodePathSegment);
|
|
if (count === 1) {
|
|
assert(node.type === "Program");
|
|
} else if (count === 2) {
|
|
assert(node.type === "FunctionDeclaration");
|
|
} else if (count === 3) {
|
|
assert(node.type === "FunctionExpression");
|
|
} else if (count === 4) {
|
|
assert(node.type === "ArrowFunctionExpression");
|
|
}
|
|
},
|
|
Program() {
|
|
assert(lastCodePathNodeType === "Program");
|
|
},
|
|
FunctionDeclaration() {
|
|
assert(lastCodePathNodeType === "FunctionDeclaration");
|
|
},
|
|
FunctionExpression() {
|
|
assert(lastCodePathNodeType === "FunctionExpression");
|
|
},
|
|
ArrowFunctionExpression() {
|
|
assert(lastCodePathNodeType === "ArrowFunctionExpression");
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"foo(); function foo() {} var foo = function() {}; var foo = () => {};",
|
|
{ rules: { test: 2 }, env: { es6: true } }
|
|
);
|
|
|
|
assert(count === 4);
|
|
});
|
|
});
|
|
|
|
describe("onCodePathSegmentEnd", () => {
|
|
it("should be fired at the end of programs/functions for the final segment", () => {
|
|
let count = 0;
|
|
let lastNodeType = null;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentEnd(cp, node) {
|
|
count += 1;
|
|
|
|
assert(cp instanceof CodePathSegment);
|
|
if (count === 4) {
|
|
assert(node.type === "Program");
|
|
} else if (count === 1) {
|
|
assert(node.type === "FunctionDeclaration");
|
|
} else if (count === 2) {
|
|
assert(node.type === "FunctionExpression");
|
|
} else if (count === 3) {
|
|
assert(node.type === "ArrowFunctionExpression");
|
|
}
|
|
assert(node.type === lastNodeType);
|
|
},
|
|
"Program:exit"() {
|
|
lastNodeType = "Program";
|
|
},
|
|
"FunctionDeclaration:exit"() {
|
|
lastNodeType = "FunctionDeclaration";
|
|
},
|
|
"FunctionExpression:exit"() {
|
|
lastNodeType = "FunctionExpression";
|
|
},
|
|
"ArrowFunctionExpression:exit"() {
|
|
lastNodeType = "ArrowFunctionExpression";
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"foo(); function foo() {} var foo = function() {}; var foo = () => {};",
|
|
{ rules: { test: 2 }, env: { es6: true } }
|
|
);
|
|
|
|
assert(count === 4);
|
|
});
|
|
});
|
|
|
|
describe("onCodePathSegmentLoop", () => {
|
|
it("should be fired in `while` loops", () => {
|
|
let count = 0;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentLoop(fromSegment, toSegment, node) {
|
|
count += 1;
|
|
assert(fromSegment instanceof CodePathSegment);
|
|
assert(toSegment instanceof CodePathSegment);
|
|
assert(node.type === "WhileStatement");
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"while (a) { foo(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
|
|
assert(count === 1);
|
|
});
|
|
|
|
it("should be fired in `do-while` loops", () => {
|
|
let count = 0;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentLoop(fromSegment, toSegment, node) {
|
|
count += 1;
|
|
assert(fromSegment instanceof CodePathSegment);
|
|
assert(toSegment instanceof CodePathSegment);
|
|
assert(node.type === "DoWhileStatement");
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"do { foo(); } while (a);",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
|
|
assert(count === 1);
|
|
});
|
|
|
|
it("should be fired in `for` loops", () => {
|
|
let count = 0;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentLoop(fromSegment, toSegment, node) {
|
|
count += 1;
|
|
assert(fromSegment instanceof CodePathSegment);
|
|
assert(toSegment instanceof CodePathSegment);
|
|
|
|
if (count === 1) {
|
|
|
|
// connect path: "update" -> "test"
|
|
assert(node.parent.type === "ForStatement");
|
|
} else if (count === 2) {
|
|
assert(node.type === "ForStatement");
|
|
}
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"for (var i = 0; i < 10; ++i) { foo(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
|
|
assert(count === 2);
|
|
});
|
|
|
|
it("should be fired in `for-in` loops", () => {
|
|
let count = 0;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentLoop(fromSegment, toSegment, node) {
|
|
count += 1;
|
|
assert(fromSegment instanceof CodePathSegment);
|
|
assert(toSegment instanceof CodePathSegment);
|
|
|
|
if (count === 1) {
|
|
|
|
// connect path: "right" -> "left"
|
|
assert(node.parent.type === "ForInStatement");
|
|
} else if (count === 2) {
|
|
assert(node.type === "ForInStatement");
|
|
}
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"for (var k in obj) { foo(); }",
|
|
{ rules: { test: 2 } }
|
|
);
|
|
|
|
assert(count === 2);
|
|
});
|
|
|
|
it("should be fired in `for-of` loops", () => {
|
|
let count = 0;
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathSegmentLoop(fromSegment, toSegment, node) {
|
|
count += 1;
|
|
assert(fromSegment instanceof CodePathSegment);
|
|
assert(toSegment instanceof CodePathSegment);
|
|
|
|
if (count === 1) {
|
|
|
|
// connect path: "right" -> "left"
|
|
assert(node.parent.type === "ForOfStatement");
|
|
} else if (count === 2) {
|
|
assert(node.type === "ForOfStatement");
|
|
}
|
|
}
|
|
}));
|
|
linter.verify(
|
|
"for (var x of xs) { foo(); }",
|
|
{ rules: { test: 2 }, env: { es6: true } }
|
|
);
|
|
|
|
assert(count === 2);
|
|
});
|
|
});
|
|
|
|
describe("completed code paths are correct", () => {
|
|
const testDataDir = path.join(__dirname, "../../../fixtures/code-path-analysis/");
|
|
const testDataFiles = fs.readdirSync(testDataDir);
|
|
|
|
testDataFiles.forEach(file => {
|
|
it(file, () => {
|
|
const source = fs.readFileSync(path.join(testDataDir, file), { encoding: "utf8" });
|
|
const expected = getExpectedDotArrows(source);
|
|
const actual = [];
|
|
|
|
assert(expected.length > 0, "/*expected */ comments not found.");
|
|
|
|
linter.defineRule("test", () => ({
|
|
onCodePathEnd(codePath) {
|
|
actual.push(debug.makeDotArrows(codePath));
|
|
}
|
|
}));
|
|
const messages = linter.verify(source, {
|
|
parserOptions: { ecmaVersion: 2020 },
|
|
rules: { test: 2 }
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong.");
|
|
|
|
for (let i = 0; i < actual.length; ++i) {
|
|
assert.strictEqual(actual[i], expected[i]);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|