gherkin-lint-ts
Version:
Gherkin features linter written in Typescript
171 lines • 6.85 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isErrorInResults = exports.lint = exports.readAndParseFile = void 0;
const logger = __importStar(require("./utils/logger"));
const types_1 = require("./types");
const rules_1 = require("./utils/rules");
const _ = require("lodash");
const Gherkin = require("gherkin").default;
const fs = require("fs");
function readAndParseFile(filePath) {
let feature;
let fileContent = [];
const parsingErrors = [];
return new Promise((resolve, reject) => {
const options = {
includeGherkinDocument: true,
includePickles: false,
includeSource: true,
};
const stream = Gherkin.fromPaths([filePath], options);
stream.on("data", (envelope) => {
var _a, _b;
if (envelope.attachment) {
// An attachment implies that there was a parsing error
parsingErrors.push(envelope.attachment);
}
else {
if ((_a = envelope.gherkinDocument) === null || _a === void 0 ? void 0 : _a.feature) {
feature = envelope.gherkinDocument.feature;
}
if (envelope.source) {
fileContent = ((_b = envelope.source.data) === null || _b === void 0 ? void 0 : _b.split(/\r\n|\r|\n/)) || [];
}
}
});
stream.on("error", data => {
logger.error(`Gherkin emitted an error while parsing ${filePath}: ${data}`);
let error = { data: data };
reject(processFatalErrors([error]));
});
stream.on("end", () => {
if (parsingErrors.length) {
// Process all errors/attachments at once, because a tag on a background will
// generate multiple error events, and it would be confusing to print a message for each
// one of them, when they are all caused by a single cause
reject(processFatalErrors(parsingErrors));
}
else {
const file = {
relativePath: filePath,
lines: fileContent,
};
resolve({ feature, file });
}
});
});
}
exports.readAndParseFile = readAndParseFile;
function lint(files, configuration, additionalRulesDirs) {
let results = [];
return Promise.all(files.map((f) => {
let perFileErrors = [];
return readAndParseFile(f)
.then(
// Handle Promise.resolve
({ feature, file }) => {
perFileErrors = rules_1.runAllEnabledRules(feature, file, configuration, additionalRulesDirs);
},
// Handle Promise.reject
(parsingErrors) => {
perFileErrors = parsingErrors;
})
.finally(() => {
const fileBlob = {
filePath: fs.realpathSync(f),
errors: _.sortBy(perFileErrors, "line"),
};
results.push(fileBlob);
});
})).then(() => results);
}
exports.lint = lint;
function processFatalErrors(errors) {
let errorMsgs = [];
if (errors.length > 1) {
const result = getFormattedTaggedBackgroundError(errors);
errors = result.errors;
errorMsgs = result.errorMsgs;
}
errors.forEach(error => {
errorMsgs.push(getFormattedFatalError(error));
});
return errorMsgs;
}
function getFormattedTaggedBackgroundError(errors) {
const errorMsgs = [];
let index = 0;
if (errors[0].data.includes("got 'Background") &&
errors[1].data.includes("expected: #TagLine, #ScenarioLine, #Comment, #Empty")) {
errorMsgs.push({
message: "Tags on Backgrounds are disallowed",
rule: "no-tags-on-backgrounds",
line: parseInt(errors[0].data.match(/\((\d+):.*/)[1]),
});
index = 2;
for (let i = 2; i < errors.length; i++) {
if (errors[i].data.includes("expected: #TagLine, #ScenarioLine, #Comment, #Empty")) {
index = i + 1;
}
else {
break;
}
}
}
return { errors: errors.slice(index), errorMsgs: errorMsgs };
}
/*eslint no-console: "off"*/
function getFormattedFatalError(error) {
const errorLine = parseInt(error.data.match(/\((\d+):.*/)[1]);
let errorMsg;
let rule;
if (error.data.includes("got 'Background")) {
errorMsg = 'Multiple "Background" definitions in the same file are disallowed';
rule = "up-to-one-background-per-file";
}
else if (error.data.includes("got 'Feature")) {
errorMsg = 'Multiple "Feature" definitions in the same file are disallowed';
rule = "one-feature-per-file";
}
else if (error.data.includes("expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got") ||
error.data.includes("expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ExamplesLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got")) {
errorMsg = 'Steps should begin with "Given", "When", "Then", "And" or "But". Multiline steps are disallowed';
rule = "no-multiline-steps";
}
else {
errorMsg = error.data;
rule = "unexpected-error";
}
return {
message: errorMsg,
rule: rule,
line: errorLine,
};
}
function isErrorInResults(results) {
return results.some(function (result) {
var _a, _b;
return (((_a = result === null || result === void 0 ? void 0 : result.errors) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0 && ((_b = result === null || result === void 0 ? void 0 : result.errors) === null || _b === void 0 ? void 0 : _b.some(e => e.errorLevel === types_1.ErrorLevel.Error));
});
}
exports.isErrorInResults = isErrorInResults;
//# sourceMappingURL=linter.js.map