Comparing version 3.3.1 to 4.0.0
@@ -5,3 +5,3 @@ #!/usr/bin/env node | ||
const path = require('path'); | ||
const { red, yellow, underline } = require('chalk'); | ||
const { red, yellow, underline, bold } = require('chalk'); | ||
@@ -17,4 +17,16 @@ const { promisify } = require('util'); | ||
const Table = require('cli-table'); | ||
const pluralize = require('pluralize'); | ||
const moddle = new BpmnModdle(); | ||
function boldRed(str) { | ||
return bold(red(str)); | ||
} | ||
function boldYellow(str) { | ||
return bold(yellow(str)); | ||
} | ||
/** | ||
@@ -24,5 +36,5 @@ * Reads XML form path and return moddle object | ||
*/ | ||
function getModdleFromXML(source) { | ||
function parseDiagram(diagramXML) { | ||
return new Promise((resolve, reject) => { | ||
moddle.fromXML(source, (err, root) => { | ||
moddle.fromXML(diagramXML, (err, moddleElement) => { | ||
if (err) { | ||
@@ -32,3 +44,3 @@ return reject(new Error('failed to parse XML', err)); | ||
return resolve(root); | ||
return resolve(moddleElement); | ||
}); | ||
@@ -38,25 +50,51 @@ }); | ||
/** | ||
* Logs formatted warning message | ||
*/ | ||
const logWarning = warning => | ||
console.log( | ||
`${yellow('warning:')} ${underline(warning.id)} ${warning.message}` | ||
); | ||
const categoryMap = { | ||
warn: 'warning' | ||
}; | ||
/** | ||
* Logs a formatted error message | ||
* Logs a formatted message | ||
*/ | ||
const logError = error => | ||
console.log(`${red('error:')} ${underline(error.id)} ${error.message}`); | ||
function tableEntry(report) { | ||
const category = report.category; | ||
const logRule = ruleName => | ||
console.log(`\nrule: ${ruleName}`); | ||
const color = category === 'error' ? red : yellow; | ||
return [ report.id, color(categoryMap[category] || category), report.message, report.name ]; | ||
} | ||
function createTable() { | ||
return new Table({ | ||
chars: { | ||
'top': '', | ||
'top-mid': '', | ||
'top-left': '', | ||
'top-right': '', | ||
'bottom': '', | ||
'bottom-mid': '', | ||
'bottom-left': '', | ||
'bottom-right': '', | ||
'left': ' ', | ||
'left-mid': '', | ||
'mid': '', | ||
'mid-mid': '', | ||
'right': '', | ||
'right-mid': '', | ||
'middle': ' ' | ||
}, | ||
style: { | ||
'padding-left': 0, | ||
'padding-right': 0 | ||
} | ||
}); | ||
} | ||
/** | ||
* Logs errors and warnings properly in the console | ||
* @param {*} errors | ||
* @param {*} warnings | ||
* Prints lint results to the console | ||
* | ||
* @param {String} filePath | ||
* @param {Object} results | ||
*/ | ||
const logReports = (results) => { | ||
function printReports(filePath, results) { | ||
@@ -66,15 +104,25 @@ let errorCount = 0; | ||
Object.entries(results).forEach(function([ ruleName, reports ]) { | ||
const table = createTable(); | ||
logRule(ruleName); | ||
Object.entries(results).forEach(function([ name, reports ]) { | ||
reports.forEach(function(report) { | ||
if (report.category === 'error') { | ||
const { | ||
category, | ||
id, | ||
message | ||
} = report; | ||
table.push(tableEntry({ | ||
category, | ||
id, | ||
message, | ||
name | ||
})); | ||
if (category === 'error') { | ||
errorCount++; | ||
logError(report); | ||
} else { | ||
warningCount++; | ||
logWarning(report); | ||
} | ||
@@ -84,13 +132,20 @@ }); | ||
console.log(`\nfound ${errorCount} errors and ${warningCount} warnings`); | ||
const problemCount = warningCount + errorCount; | ||
if (errorCount > 0) { | ||
process.exit(1); | ||
if (problemCount) { | ||
console.log(); | ||
console.log(underline(path.resolve(filePath))); | ||
console.log(table.toString()); | ||
} | ||
}; | ||
return { | ||
errorCount, | ||
warningCount | ||
}; | ||
} | ||
const cli = meow( | ||
` | ||
Usage | ||
$ bpmnlint <file.bpmn> | ||
$ bpmnlint diagram.bpmn | ||
@@ -115,3 +170,3 @@ Options | ||
if (cli.input.length !== 1) { | ||
if (cli.input.length === 0) { | ||
console.log('Error: bpmn file path missing.'); | ||
@@ -127,21 +182,14 @@ process.exit(1); | ||
async function handleConfig(configString) { | ||
let config; | ||
async function lintDiagram(diagramPath, config) { | ||
try { | ||
config = JSON.parse(configString); | ||
} catch (e) { | ||
return logAndExit('Error: Could not parse configuration file', e); | ||
} | ||
let diagramXML; | ||
try { | ||
diagramXML = await readFile(path.resolve(cli.input[0]), 'utf-8'); | ||
diagramXML = await readFile(path.resolve(diagramPath), 'utf-8'); | ||
} catch (e) { | ||
return logAndExit(`Error: Failed to read ${cli.input[0]}`, e); | ||
return logAndExit(`Error: Failed to read ${diagramPath}`, e); | ||
} | ||
try { | ||
const moddleRoot = await getModdleFromXML(diagramXML); | ||
const moddleRoot = await parseDiagram(diagramXML); | ||
@@ -155,3 +203,3 @@ const linter = new Linter({ | ||
logReports(lintResults); | ||
return printReports(diagramPath, lintResults); | ||
} catch (e) { | ||
@@ -162,2 +210,48 @@ return logAndExit(e); | ||
async function handleConfig(configString) { | ||
console.log(); | ||
let config; | ||
try { | ||
config = JSON.parse(configString); | ||
} catch (e) { | ||
return logAndExit('Error: Could not parse configuration file', e); | ||
} | ||
let errorCount = 0; | ||
let warningCount = 0; | ||
for (let i = 0; i < cli.input.length; i++) { | ||
let results = await lintDiagram(cli.input[i], config); | ||
errorCount += results.errorCount; | ||
warningCount += results.warningCount; | ||
} | ||
const problemCount = errorCount + warningCount; | ||
let color; | ||
if (warningCount) { | ||
color = boldYellow; | ||
} | ||
if (errorCount) { | ||
color = boldRed; | ||
} | ||
if (problemCount) { | ||
console.log(); | ||
console.log(color( | ||
`✖ ${problemCount} ${pluralize('problem', problemCount)} (${errorCount} ${pluralize('error', errorCount)}, ${warningCount} ${pluralize('warning', warningCount)})` | ||
)); | ||
} | ||
if (errorCount) { | ||
process.exit(1); | ||
} | ||
} | ||
const { config } = cli.flags; | ||
@@ -164,0 +258,0 @@ |
@@ -9,2 +9,9 @@ # Changelog | ||
## 4.0.0 | ||
* `FEAT`: add ability to batch lint multiple files via CLI | ||
* `FEAT`: don't resolve disabled rules ([`6c45f3f9`](https://github.com/bpmn-io/bpmnlint/commit/6c45f3f952a412dda05deb5c57861a1c76af23bb)) | ||
* `CHORE`: unify names of built-in rules | ||
* `CHORE`: adopt cli output to eslint styling | ||
## 3.3.1 | ||
@@ -11,0 +18,0 @@ |
const testRule = require('./test-rule'); | ||
const utils = require('./utils'); | ||
const flagMap = { | ||
const categoryMap = { | ||
0: 'off', | ||
@@ -35,15 +35,9 @@ 1: 'warn', | ||
* | ||
* @param {ModdleElement} options.moddleRoot | ||
* @param {String} options.ruleFlag | ||
* @param {Rule} options.rule | ||
* @param {Object} options.ruleConfig | ||
* @param {ModdleElement} moddleRoot | ||
* @param {Rule} rule | ||
* @param {Object} ruleConfig | ||
* | ||
* @return {Array<ValidationErrors>} lint results | ||
*/ | ||
Linter.prototype.applyRule = function applyRule({ moddleRoot, ruleFlag, rule, ruleConfig }) { | ||
if (ruleFlag === 'off') { | ||
return []; | ||
} | ||
Linter.prototype.applyRule = function applyRule(moddleRoot, rule, ruleConfig) { | ||
return testRule({ moddleRoot, rule, ruleConfig }); | ||
@@ -110,3 +104,3 @@ }; | ||
* | ||
* @param {Object} rulesConfig | ||
* @param {Object} config | ||
* | ||
@@ -118,14 +112,36 @@ * @return {Array<RuleDefinition>} | ||
return this.resolveConfiguredRules(config).then((rulesConfig) => { | ||
return Promise.all( | ||
Object.entries(rulesConfig).map(([ name, value ]) => { | ||
return this.resolveRule(name).then(function(rule) { | ||
return { | ||
value, | ||
name, | ||
rule | ||
}; | ||
}); | ||
}) | ||
); | ||
// parse rule values | ||
const parsedRules = Object.entries(rulesConfig).map(([ name, value ]) => { | ||
const { | ||
category, | ||
config | ||
} = this.parseRuleValue(value); | ||
return { | ||
name, | ||
category, | ||
config | ||
}; | ||
}); | ||
// filter only for enabled rules | ||
const enabledRules = parsedRules.filter(definition => definition.category !== 'off'); | ||
// load enabled rules | ||
const loaders = enabledRules.map((definition) => { | ||
const { | ||
name | ||
} = definition; | ||
return this.resolveRule(name).then(function(rule) { | ||
return { | ||
...definition, | ||
rule | ||
}; | ||
}); | ||
}); | ||
return Promise.all(loaders); | ||
}); | ||
@@ -186,10 +202,12 @@ }; | ||
ruleDefinitions.forEach(({ rule, name, value }) => { | ||
ruleDefinitions.forEach((ruleDefinition) => { | ||
const { | ||
ruleFlag, | ||
ruleConfig | ||
} = this.parseRuleValue(value); | ||
rule, | ||
name, | ||
config, | ||
category | ||
} = ruleDefinition; | ||
const reports = this.applyRule({ moddleRoot, ruleFlag, rule, ruleConfig }); | ||
const reports = this.applyRule(moddleRoot, rule, config); | ||
@@ -203,3 +221,3 @@ if (reports.length === 0) { | ||
...report, | ||
category: ruleFlag | ||
category | ||
}; | ||
@@ -218,11 +236,11 @@ }); | ||
let ruleFlag; | ||
let ruleConfig; | ||
let category; | ||
let config; | ||
if (Array.isArray(value)) { | ||
ruleFlag = value[0]; | ||
ruleConfig = value[1]; | ||
category = value[0]; | ||
config = value[1]; | ||
} else { | ||
ruleFlag = value; | ||
ruleConfig = {}; | ||
category = value; | ||
config = {}; | ||
} | ||
@@ -232,11 +250,11 @@ | ||
// may be upper case or a number at this point | ||
if (typeof ruleFlag === 'string') { | ||
ruleFlag = ruleFlag.toLowerCase(); | ||
if (typeof category === 'string') { | ||
category = category.toLowerCase(); | ||
} | ||
ruleFlag = flagMap[ruleFlag] || ruleFlag; | ||
category = categoryMap[category] || category; | ||
return { | ||
ruleConfig, | ||
ruleFlag | ||
config, | ||
category | ||
}; | ||
@@ -243,0 +261,0 @@ }; |
@@ -25,5 +25,5 @@ const Linter = require('../linter'); | ||
const { ruleFlag } = linter.parseRuleValue(value); | ||
const { category } = linter.parseRuleValue(value); | ||
if (ruleFlag !== 'off') { | ||
if (category !== 'off') { | ||
enabledRules[key] = value; | ||
@@ -30,0 +30,0 @@ } |
{ | ||
"name": "bpmnlint", | ||
"version": "3.3.1", | ||
"version": "4.0.0", | ||
"main": "lib/index.js", | ||
@@ -37,3 +37,5 @@ "keywords": [ | ||
"chalk": "^2.4.1", | ||
"meow": "^5.0.0" | ||
"cli-table": "^0.3.1", | ||
"meow": "^5.0.0", | ||
"pluralize": "^7.0.0" | ||
}, | ||
@@ -40,0 +42,0 @@ "devDependencies": { |
@@ -12,17 +12,14 @@ # bpmnlint | ||
```zsh | ||
> bpmnlint diagram.bpmn | ||
```sh | ||
> bpmnlint invoice.bpmn | ||
rule: bpmnlint/label-required | ||
error: sid-E391B624-F6E8-428B-9C3E-7026F85C4F24 is missing label/name | ||
error: sid-3E1FA189-AC8C-4CF1-9057-3D2EF8C6D3AF is missing label/name | ||
error: sid-DD0BC4E1-4AA3-4835-A477-373EA263A593 is missing label/name | ||
error: sid-B8B18E3A-EF8D-4D19-B5CD-C666D39E2E0D is missing label/name | ||
error: sid-994BB7B0-64D8-4DC4-B549-0758628F5A16 is missing label/name | ||
/Projects/process-application/resources/invoice.bpmn | ||
Flow_1 error Sequence flow is missing condition conditional-flows | ||
Process error Process is missing end event end-event-required | ||
Task_13 warning Element is missing label/name label-required | ||
Event_12 warning Element is missing label/name label-required | ||
Event_27 warning Element is missing label/name label-required | ||
Process error Process is missing start event start-event-required | ||
rule: bpmnlint/no-gateway-join-fork | ||
warn: sid-545B3227-D12A-43A8-B746-55E8C75F3A8A forks and joins | ||
warn: sid-AB73793C-D47A-4738-B34F-A82C6219A92C forks and joins | ||
found 10 errors and 2 warnings | ||
✖ 6 problems (6 errors, 0 warnings) | ||
``` | ||
@@ -52,3 +49,3 @@ | ||
"rules": { | ||
"bpmnlint/label-required": "off" | ||
"label-required": "off" | ||
} | ||
@@ -55,0 +52,0 @@ } |
@@ -22,3 +22,3 @@ // verify that sequence flows outgoing from a | ||
if (missingCondition) { | ||
reporter.report(flow.id, 'is missing condition'); | ||
reporter.report(flow.id, 'Sequence flow is missing condition'); | ||
} | ||
@@ -25,0 +25,0 @@ }); |
@@ -28,3 +28,5 @@ /** | ||
if (!hasEndEvent(node)) { | ||
reporter.report(node.id, 'is missing an end event'); | ||
const type = is(node, 'bpmn:SubProcess') ? 'Sub process' : 'Process'; | ||
reporter.report(node.id, type + ' is missing end event'); | ||
} | ||
@@ -31,0 +33,0 @@ } |
@@ -25,3 +25,3 @@ // verify that start events inside an event sub-process | ||
if (eventDefinitions.length === 0) { | ||
reporter.report(flowElement.id, 'is missing event definition'); | ||
reporter.report(flowElement.id, 'Start event is missing event definition'); | ||
} | ||
@@ -28,0 +28,0 @@ }); |
@@ -23,3 +23,3 @@ // verify that no fake join is modeled by attempting | ||
if (incoming.length > 1) { | ||
reporter.report(node.id, 'does not join'); | ||
reporter.report(node.id, 'Incoming flows do not join'); | ||
} | ||
@@ -26,0 +26,0 @@ } |
@@ -18,3 +18,3 @@ | ||
if (is(node, type)) { | ||
reporter.report(node.id, 'has disallowed type <' + type + '>'); | ||
reporter.report(node.id, 'Element has disallowed type <' + type + '>'); | ||
} | ||
@@ -21,0 +21,0 @@ } |
@@ -50,3 +50,3 @@ /** | ||
if (name.length === 0) { | ||
reporter.report(node.id, 'is missing label/name'); | ||
reporter.report(node.id, 'Element is missing label/name'); | ||
} | ||
@@ -53,0 +53,0 @@ } |
@@ -24,3 +24,3 @@ // verify that there exists no disconnected | ||
if (!incoming.length && !outgoing.length) { | ||
reporter.report(node.id, 'is not connected'); | ||
reporter.report(node.id, 'Element is not connected'); | ||
} | ||
@@ -27,0 +27,0 @@ } |
@@ -18,3 +18,3 @@ // verify that a gateway does not fork and join | ||
if (incoming.length > 1 && outgoing.length > 1) { | ||
reporter.report(node.id, 'forks and joins'); | ||
reporter.report(node.id, 'Gateway forks and joins'); | ||
} | ||
@@ -21,0 +21,0 @@ } |
@@ -27,3 +27,3 @@ // verify that no implicit split is modeled | ||
if (outgoingWithoutCondition.length > 1) { | ||
reporter.report(node.id, 'must not split implicitly'); | ||
reporter.report(node.id, 'Flow splits implicitly'); | ||
} | ||
@@ -30,0 +30,0 @@ } |
@@ -28,3 +28,5 @@ // verify that there exists only a single blank | ||
if (blankStartEvents.length > 1) { | ||
reporter.report(node.id, 'contains multiple blank start events'); | ||
const type = is(node, 'bpmn:SubProcess') ? 'Sub process' : 'Process'; | ||
reporter.report(node.id, type + ' has multiple blank start events'); | ||
} | ||
@@ -31,0 +33,0 @@ } |
@@ -17,3 +17,3 @@ // verify that an event contains maximum one | ||
if (eventDefinitions.length > 1) { | ||
reporter.report(node.id, 'contains multiple event definitions'); | ||
reporter.report(node.id, 'Event has multiple event definitions'); | ||
} | ||
@@ -20,0 +20,0 @@ } |
@@ -28,3 +28,5 @@ /** | ||
if (!hasStartEvent(node)) { | ||
reporter.report(node.id, 'is missing a start event'); | ||
const type = is(node, 'bpmn:SubProcess') ? 'Sub process' : 'Process'; | ||
reporter.report(node.id, type + ' is missing start event'); | ||
} | ||
@@ -31,0 +33,0 @@ } |
@@ -25,3 +25,3 @@ // verify that start events inside a normal sub-process | ||
if (eventDefinitions.length > 0) { | ||
reporter.report(flowElement.id, 'must not contain event definition'); | ||
reporter.report(flowElement.id, 'Start event must be blank'); | ||
} | ||
@@ -28,0 +28,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36993
1129
5
67
+ Addedcli-table@^0.3.1
+ Addedpluralize@^7.0.0
+ Addedcli-table@0.3.11(transitive)
+ Addedcolors@1.0.3(transitive)
+ Addedpluralize@7.0.0(transitive)