UNPKG

gplint

Version:

A Gherkin linter/validator written in Javascript.

159 lines 6.13 kB
import fs from 'fs'; import _ from 'lodash'; import { GherkinStreams } from '@cucumber/gherkin-streams'; import * as logger from './logger.js'; import * as rules from './rules.js'; import * as configParser from './config-parser.js'; import { RuleErrors } from './errors.js'; export async function readAndParseFile(filePath) { let feature; const pickles = []; const parsingErrors = []; let fileContent = []; let fileEOL = '\n'; return new Promise((resolve, reject) => { const options = { includeGherkinDocument: true, includePickles: true, includeSource: true, }; const stream = GherkinStreams.fromPaths([filePath], options); stream.on('data', (envelope) => { if (envelope.parseError) { parsingErrors.push(envelope.parseError); } else { if (envelope.gherkinDocument?.feature) { feature = envelope.gherkinDocument.feature; } if (envelope.pickle) { pickles.push(envelope.pickle); } if (envelope.source) { fileEOL = envelope.source.data.includes('\r\n') ? '\r\n' : '\n'; fileContent = envelope.source.data.split(fileEOL); } } }); stream.on('error', data => { logger.error(`Gherkin emitted an error while parsing ${filePath}: ${data.message}`); reject(new RuleErrors(processFatalErrors([{ message: data.message }]))); }); 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(new RuleErrors(processFatalErrors(parsingErrors))); } else { const file = { relativePath: filePath, lines: fileContent, EOL: fileEOL, }; resolve({ feature, pickles, file }); } }); }); } export async function lintInit(files, args, additionalRulesDirs) { const configuration = await configParser.getConfiguration(args.config, additionalRulesDirs); return lint(files, configuration, additionalRulesDirs, args.fix); } export async function lint(files, configuration, additionalRulesDirs, autoFix) { const results = []; await Promise.all(files.map(async (f) => { let perFileErrors; try { const gherkinData = await readAndParseFile(f); perFileErrors = await rules.runAllEnabledRules(gherkinData, configuration, additionalRulesDirs, autoFix); } catch (parsingErrors) { if (parsingErrors instanceof RuleErrors) { perFileErrors = parsingErrors; } else { throw parsingErrors; } } finally { const fileErrors = { filePath: fs.realpathSync(f), errors: _.sortBy(perFileErrors?.getErrors(), 'line') }; results.push(fileErrors); } })); return results; } 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].message?.includes('got \'Background') && errors[1].message?.includes('expected: #TagLine, #RuleLine, #Comment, #Empty')) { errorMsgs.push({ message: 'Tags on Backgrounds are disallowed', rule: 'no-tags-on-backgrounds', line: parseInt((/\((\d+):.*/.exec(errors[0].message))?.[1] ?? '0'), column: 0, level: 2, // Force error }); index = 2; for (let i = 2; i < errors.length; i++) { if (errors[i].message?.includes('expected: #TagLine, #RuleLine, #Comment, #Empty')) { index = i + 1; } else { /* c8 ignore next 3 */ // IDK if this could happen, but let's keep this to be safe break; } } } return { errors: errors.slice(index), errorMsgs }; } function getFormattedFatalError(error) { const errorLine = parseInt((/\((\d+):.*/.exec(error.message))?.[1] ?? '0'); let errorMsg; let rule; if (error.message.includes('got \'Background')) { errorMsg = 'Multiple "Background" definitions in the same file are disallowed'; rule = 'up-to-one-background-per-file'; } else if (error.message.includes('got \'Feature')) { errorMsg = 'Multiple "Feature" definitions in the same file are disallowed'; rule = 'one-feature-per-file'; } else if (error.message.includes('expected: #EOF, #TableRow, #DocStringSeparator, #StepLine, #TagLine, #ScenarioLine, #RuleLine, #Comment, #Empty, got') || error.message.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 { /* c8 ignore next 4 */ // IDK if this could happen, but let's keep this to be safe errorMsg = error.message; rule = 'unexpected-error'; } return { message: errorMsg, rule, line: errorLine, column: 0, level: 2, // Force error }; } //# sourceMappingURL=linter.js.map