mirror of
https://git.proxmox.com/git/pve-eslint
synced 2025-08-18 15:21:09 +00:00
1095 lines
37 KiB
JavaScript
1095 lines
37 KiB
JavaScript
/**
|
|
* @fileoverview Tests for createReportTranslator
|
|
* @author Teddy Katz
|
|
*/
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
const assert = require("chai").assert;
|
|
const { SourceCode } = require("../../../lib/source-code");
|
|
const espree = require("espree");
|
|
const createReportTranslator = require("../../../lib/linter/report-translator");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Tests
|
|
//------------------------------------------------------------------------------
|
|
|
|
describe("createReportTranslator", () => {
|
|
|
|
/**
|
|
* Creates a SourceCode instance out of JavaScript text
|
|
* @param {string} text Source text
|
|
* @returns {SourceCode} A SourceCode instance for that text
|
|
*/
|
|
function createSourceCode(text) {
|
|
return new SourceCode(
|
|
text,
|
|
espree.parse(
|
|
text.replace(/^\uFEFF/u, ""),
|
|
{
|
|
loc: true,
|
|
range: true,
|
|
raw: true,
|
|
tokens: true,
|
|
comment: true
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
let node, location, message, translateReport, suggestion1, suggestion2;
|
|
|
|
beforeEach(() => {
|
|
const sourceCode = createSourceCode("foo\nbar");
|
|
|
|
node = sourceCode.ast.body[0];
|
|
location = sourceCode.ast.body[1].loc.start;
|
|
message = "foo";
|
|
suggestion1 = "First suggestion";
|
|
suggestion2 = "Second suggestion {{interpolated}}";
|
|
translateReport = createReportTranslator({
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
sourceCode,
|
|
messageIds: {
|
|
testMessage: message,
|
|
suggestion1,
|
|
suggestion2
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("old-style call with location", () => {
|
|
it("should extract the location correctly", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, location, message, {}),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("old-style call without location", () => {
|
|
it("should use the start location and end location of the node", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, message, {}),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("new-style call with all options", () => {
|
|
it("should include the new-style options in the report", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
fix: () => ({ range: [1, 2], text: "foo" }),
|
|
suggest: [{
|
|
desc: "suggestion 1",
|
|
fix: () => ({ range: [2, 3], text: "s1" })
|
|
}, {
|
|
desc: "suggestion 2",
|
|
fix: () => ({ range: [3, 4], text: "s2" })
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 2],
|
|
text: "foo"
|
|
},
|
|
suggestions: [{
|
|
desc: "suggestion 1",
|
|
fix: { range: [2, 3], text: "s1" }
|
|
}, {
|
|
desc: "suggestion 2",
|
|
fix: { range: [3, 4], text: "s2" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should translate the messageId into a message", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
messageId: "testMessage",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
messageId: "testMessage",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 2],
|
|
text: "foo"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should throw when both messageId and message are provided", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
messageId: "testMessage",
|
|
message: "bar",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
"context.report() called with a message and a messageId. Please only pass one."
|
|
);
|
|
});
|
|
|
|
it("should throw when an invalid messageId is provided", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
messageId: "thisIsNotASpecifiedMessageId",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
/^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/u
|
|
);
|
|
});
|
|
|
|
it("should throw when no message is provided", () => {
|
|
const reportDescriptor = { node };
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
"Missing `message` property in report() call; add a message that describes the linting problem."
|
|
);
|
|
});
|
|
|
|
it("should support messageIds for suggestions and output resulting descriptions", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
messageId: "suggestion1",
|
|
fix: () => ({ range: [2, 3], text: "s1" })
|
|
}, {
|
|
messageId: "suggestion2",
|
|
data: { interpolated: "'interpolated value'" },
|
|
fix: () => ({ range: [3, 4], text: "s2" })
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
messageId: "suggestion1",
|
|
desc: "First suggestion",
|
|
fix: { range: [2, 3], text: "s1" }
|
|
}, {
|
|
messageId: "suggestion2",
|
|
data: { interpolated: "'interpolated value'" },
|
|
desc: "Second suggestion 'interpolated value'",
|
|
fix: { range: [3, 4], text: "s2" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should throw when a suggestion defines both a desc and messageId", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "The description",
|
|
messageId: "suggestion1",
|
|
fix: () => ({ range: [2, 3], text: "s1" })
|
|
}]
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
"context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."
|
|
);
|
|
});
|
|
|
|
it("should throw when a suggestion uses an invalid messageId", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
messageId: "noMatchingMessage",
|
|
fix: () => ({ range: [2, 3], text: "s1" })
|
|
}]
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
/^context\.report\(\) called with a suggest option with a messageId '[^']+' which is not present in the 'messages' config:/u
|
|
);
|
|
});
|
|
|
|
it("should throw when a suggestion does not provide either a desc or messageId", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
fix: () => ({ range: [2, 3], text: "s1" })
|
|
}]
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
"context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"
|
|
);
|
|
});
|
|
|
|
it("should throw when a suggestion does not provide a fix function", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "The description",
|
|
fix: false
|
|
}]
|
|
};
|
|
|
|
assert.throws(
|
|
() => translateReport(reportDescriptor),
|
|
TypeError,
|
|
/^context\.report\(\) called with a suggest option without a fix function. See:/u
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("combining autofixes", () => {
|
|
it("should merge fixes to one if 'fix' function returns an array of fixes.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 5],
|
|
text: "fooo\nbar"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should merge fixes to one if 'fix' function returns an iterator of fixes.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
*fix() {
|
|
yield { range: [1, 2], text: "foo" };
|
|
yield { range: [4, 5], text: "bar" };
|
|
}
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 5],
|
|
text: "fooo\nbar"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should respect ranges of empty insertions when merging fixes to one.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
*fix() {
|
|
yield { range: [4, 5], text: "cd" };
|
|
yield { range: [2, 2], text: "" };
|
|
yield { range: [7, 7], text: "" };
|
|
}
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [2, 7],
|
|
text: "o\ncdar"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should pass through fixes if only one is present", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
fix: () => [{ range: [1, 2], text: "foo" }]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 2],
|
|
text: "foo"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should handle inserting BOM correctly.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [4, 5], text: "x" }]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [0, 5],
|
|
text: "\uFEFFfoo\nx"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
|
|
it("should handle removing BOM correctly.", () => {
|
|
const sourceCode = createSourceCode("\uFEFFfoo\nbar");
|
|
|
|
node = sourceCode.ast.body[0];
|
|
|
|
const reportDescriptor = {
|
|
node,
|
|
message,
|
|
fix: () => [{ range: [-1, 3], text: "foo" }, { range: [4, 5], text: "x" }]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
createReportTranslator({ ruleId: "foo-rule", severity: 1, sourceCode })(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 1,
|
|
message: "foo",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [-1, 5],
|
|
text: "foo\nx"
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should throw an assertion error if ranges are overlapped.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [2, 5], text: "x" }]
|
|
};
|
|
|
|
assert.throws(
|
|
translateReport.bind(null, reportDescriptor),
|
|
"Fix objects must not be overlapped in a report."
|
|
);
|
|
});
|
|
|
|
it("should include a fix passed as the last argument when location is passed", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(
|
|
node,
|
|
{ line: 42, column: 23 },
|
|
"my message {{1}}{{0}}",
|
|
["!", "testing"],
|
|
() => ({ range: [1, 1], text: "" })
|
|
),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "my message testing!",
|
|
line: 42,
|
|
column: 24,
|
|
nodeType: "ExpressionStatement",
|
|
fix: {
|
|
range: [1, 1],
|
|
text: ""
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("suggestions", () => {
|
|
it("should support multiple suggestions.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A first suggestion for the issue",
|
|
fix: () => [{ range: [1, 2], text: "foo" }]
|
|
}, {
|
|
desc: "A different suggestion for the issue",
|
|
fix: () => [{ range: [1, 3], text: "foobar" }]
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
desc: "A first suggestion for the issue",
|
|
fix: { range: [1, 2], text: "foo" }
|
|
}, {
|
|
desc: "A different suggestion for the issue",
|
|
fix: { range: [1, 3], text: "foobar" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A suggestion for the issue",
|
|
fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }]
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
desc: "A suggestion for the issue",
|
|
fix: {
|
|
range: [1, 5],
|
|
text: "fooo\nbar"
|
|
}
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A suggestion for the issue",
|
|
fix: () => null
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A suggestion for the issue",
|
|
fix: () => []
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A suggestion for the issue",
|
|
*fix() {}
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
|
|
// This isn't officially supported, but autofix works the same way
|
|
it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "A suggestion for the issue",
|
|
fix() {}
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement"
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should keep suggestion before a removed suggestion.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "Suggestion with a fix",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
}, {
|
|
desc: "Suggestion without a fix",
|
|
fix: () => null
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
desc: "Suggestion with a fix",
|
|
fix: { range: [1, 2], text: "foo" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should keep suggestion after a removed suggestion.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "Suggestion without a fix",
|
|
fix: () => null
|
|
}, {
|
|
desc: "Suggestion with a fix",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
desc: "Suggestion with a fix",
|
|
fix: { range: [1, 2], text: "foo" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
|
|
const reportDescriptor = {
|
|
node,
|
|
loc: location,
|
|
message,
|
|
suggest: [{
|
|
desc: "Keep #1",
|
|
fix: () => ({ range: [1, 2], text: "foo" })
|
|
}, {
|
|
desc: "Remove #1",
|
|
fix() {
|
|
return null;
|
|
}
|
|
}, {
|
|
desc: "Keep #2",
|
|
fix: () => ({ range: [1, 2], text: "bar" })
|
|
}, {
|
|
desc: "Remove #2",
|
|
fix() {
|
|
return [];
|
|
}
|
|
}, {
|
|
desc: "Keep #3",
|
|
fix: () => ({ range: [1, 2], text: "baz" })
|
|
}, {
|
|
desc: "Remove #3",
|
|
*fix() {}
|
|
}, {
|
|
desc: "Keep #4",
|
|
fix: () => ({ range: [1, 2], text: "quux" })
|
|
}]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
translateReport(reportDescriptor),
|
|
{
|
|
ruleId: "foo-rule",
|
|
severity: 2,
|
|
message: "foo",
|
|
line: 2,
|
|
column: 1,
|
|
nodeType: "ExpressionStatement",
|
|
suggestions: [{
|
|
desc: "Keep #1",
|
|
fix: { range: [1, 2], text: "foo" }
|
|
}, {
|
|
desc: "Keep #2",
|
|
fix: { range: [1, 2], text: "bar" }
|
|
}, {
|
|
desc: "Keep #3",
|
|
fix: { range: [1, 2], text: "baz" }
|
|
}, {
|
|
desc: "Keep #4",
|
|
fix: { range: [1, 2], text: "quux" }
|
|
}]
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("message interpolation", () => {
|
|
it("should correctly parse a message when being passed all options in an old-style report", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, node.loc.end, "hello {{dynamic}}", { dynamic: node.type }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello ExpressionStatement",
|
|
nodeType: "ExpressionStatement",
|
|
line: 1,
|
|
column: 4
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should correctly parse a message when being passed all options in a new-style report", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ node, loc: node.loc.end, message: "hello {{dynamic}}", data: { dynamic: node.type } }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello ExpressionStatement",
|
|
nodeType: "ExpressionStatement",
|
|
line: 1,
|
|
column: 4
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should correctly parse a message with object keys as numbers", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "my message {{name}}{{0}}", { 0: "!", name: "testing" }).message,
|
|
"my message testing!"
|
|
);
|
|
});
|
|
|
|
it("should correctly parse a message with array", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "my message {{1}}{{0}}", ["!", "testing"]).message,
|
|
"my message testing!"
|
|
);
|
|
});
|
|
|
|
it("should allow template parameter with inner whitespace", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{parameter name}}", { "parameter name": "yay!" }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
|
|
it("should allow template parameter with non-identifier characters", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{parameter-name}}", { "parameter-name": "yay!" }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
|
|
it("should allow template parameter wrapped in braces", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{{param}}}", { param: "yay!" }).message,
|
|
"message {yay!}"
|
|
);
|
|
});
|
|
|
|
it("should ignore template parameter with no specified value", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{parameter}}", {}).message,
|
|
"message {{parameter}}"
|
|
);
|
|
});
|
|
|
|
it("should handle leading whitespace in template parameter", () => {
|
|
assert.strictEqual(
|
|
translateReport({ node, message: "message {{ parameter}}", data: { parameter: "yay!" } }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
|
|
it("should handle trailing whitespace in template parameter", () => {
|
|
assert.strictEqual(
|
|
translateReport({ node, message: "message {{parameter }}", data: { parameter: "yay!" } }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
|
|
it("should still allow inner whitespace as well as leading/trailing", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{ parameter name }}", { "parameter name": "yay!" }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
|
|
it("should still allow non-identifier characters as well as leading/trailing whitespace", () => {
|
|
assert.strictEqual(
|
|
translateReport(node, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message,
|
|
"message yay!"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("location inference", () => {
|
|
it("should use the provided location when given in an old-style call", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, { line: 42, column: 13 }, "hello world"),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: "ExpressionStatement",
|
|
line: 42,
|
|
column: 14
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should use the provided location when given in an new-style call", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ node, loc: { line: 42, column: 13 }, message: "hello world" }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: "ExpressionStatement",
|
|
line: 42,
|
|
column: 14
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should extract the start and end locations from a node if no location is provided", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, "hello world"),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: "ExpressionStatement",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ loc: node.loc, message: "hello world" }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: null,
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should not have 'endLine' and 'endColumn' when 'loc' property does not have 'end' property.", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ loc: node.loc.start, message: "hello world" }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: null,
|
|
line: 1,
|
|
column: 1
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should infer an 'endLine' and 'endColumn' property when using the object-based context.report API", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ node, message: "hello world" }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: "ExpressionStatement",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("converting old-style calls", () => {
|
|
it("should include a fix passed as the last argument when location is not passed", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(node, "my message {{1}}{{0}}", ["!", "testing"], () => ({ range: [1, 1], text: "" })),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "my message testing!",
|
|
nodeType: "ExpressionStatement",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 4,
|
|
fix: { range: [1, 1], text: "" }
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("validation", () => {
|
|
|
|
it("should throw an error if node is not an object", () => {
|
|
assert.throws(
|
|
() => translateReport("not a node", "hello world"),
|
|
"Node must be an object"
|
|
);
|
|
});
|
|
|
|
|
|
it("should not throw an error if location is provided and node is not in an old-style call", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport(null, { line: 1, column: 1 }, "hello world"),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: null,
|
|
line: 1,
|
|
column: 2
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should not throw an error if location is provided and node is not in a new-style call", () => {
|
|
assert.deepStrictEqual(
|
|
translateReport({ loc: { line: 1, column: 1 }, message: "hello world" }),
|
|
{
|
|
severity: 2,
|
|
ruleId: "foo-rule",
|
|
message: "hello world",
|
|
nodeType: null,
|
|
line: 1,
|
|
column: 2
|
|
}
|
|
);
|
|
});
|
|
|
|
it("should throw an error if neither node nor location is provided", () => {
|
|
assert.throws(
|
|
() => translateReport(null, "hello world"),
|
|
"Node must be provided when reporting error if location is not provided"
|
|
);
|
|
});
|
|
|
|
it("should throw an error if fix range is invalid", () => {
|
|
assert.throws(
|
|
() => translateReport({ node, messageId: "testMessage", fix: () => ({ text: "foo" }) }),
|
|
"Fix has invalid range"
|
|
);
|
|
|
|
for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
|
|
assert.throws(
|
|
// eslint-disable-next-line no-loop-func -- Using arrow functions
|
|
() => translateReport(
|
|
{ node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) }
|
|
),
|
|
"Fix has invalid range"
|
|
);
|
|
|
|
assert.throws(
|
|
// eslint-disable-next-line no-loop-func -- Using arrow functions
|
|
() => translateReport(
|
|
{
|
|
node,
|
|
messageId: "testMessage",
|
|
fix: () => [
|
|
{ range: [0, 0], text: "foo" },
|
|
{ range: badRange, text: "bar" },
|
|
{ range: [1, 1], text: "baz" }
|
|
]
|
|
}
|
|
),
|
|
"Fix has invalid range"
|
|
);
|
|
}
|
|
});
|
|
});
|
|
});
|