Comparing version 1.0.0-alpha4 to 1.0.0-alpha5
#!/usr/bin/env node | ||
const meow = require("meow"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const { red, yellow, underline } = require("chalk"); | ||
const BpmnModdle = require("bpmn-moddle"); | ||
const meow = require('meow'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const { red, yellow, underline } = require('chalk'); | ||
const BpmnModdle = require('bpmn-moddle'); | ||
const { promisify } = require("util"); | ||
const { promisify } = require('util'); | ||
const readFile = promisify(fs.readFile); | ||
const linter = require("../lib/linter"); | ||
const Linter = require('../lib/linter'); | ||
const moddle = new BpmnModdle(); | ||
const nodeResolver = require('../lib/resolver/nodeResolver'); | ||
/** | ||
@@ -19,8 +21,7 @@ * Reads XML form path and return moddle object | ||
*/ | ||
async function getModdleRoot(sourcePath) { | ||
const source = await readFile(path.resolve(sourcePath), "utf-8"); | ||
function getModdleFromXML(source) { | ||
return new Promise((resolve, reject) => { | ||
moddle.fromXML(source, (err, root) => { | ||
if (err) { | ||
return reject(new Error("failed to parse XML", err)); | ||
return reject(new Error('failed to parse XML', err)); | ||
} | ||
@@ -38,3 +39,3 @@ | ||
console.log( | ||
`${yellow("warning:")} ${underline(warning.id)} ${warning.message}` | ||
`${yellow('warning:')} ${underline(warning.id)} ${warning.message}` | ||
); | ||
@@ -46,3 +47,3 @@ | ||
const logError = error => | ||
console.log(`${red("error:")} ${underline(error.id)} ${error.message}`); | ||
console.log(`${red('error:')} ${underline(error.id)} ${error.message}`); | ||
@@ -69,3 +70,3 @@ /** | ||
$ bpmnlint ./invoice.bpmn | ||
`, | ||
@@ -75,4 +76,4 @@ { | ||
config: { | ||
type: "string", | ||
alias: "c" | ||
type: 'string', | ||
alias: 'c' | ||
} | ||
@@ -86,23 +87,48 @@ } | ||
if (cli.input.length !== 1) { | ||
console.log("Error: bpmn file path missing."); | ||
console.log('Error: bpmn file path missing.'); | ||
process.exit(1); | ||
} | ||
function logAndExit(...args) { | ||
console.error(...args); | ||
process.exit(1); | ||
} | ||
async function handleConfig(config) { | ||
let parsedConfig; | ||
try { | ||
const parsedConfig = JSON.parse(config); | ||
const moddleRoot = await getModdleRoot(cli.input[0]); | ||
linter({ moddleRoot, config: parsedConfig }) | ||
.then(logReports) | ||
.catch(console.error); | ||
parsedConfig = JSON.parse(config); | ||
} catch (e) { | ||
console.log(`Error parsing the configuration file: ${e}`); | ||
return logAndExit('Error: Could not parse configuration file', e); | ||
} | ||
let diagramXML; | ||
try { | ||
diagramXML = await readFile(path.resolve(cli.input[0]), 'utf-8'); | ||
} catch (e) { | ||
return logAndExit(`Error: Failed to read ${cli.input[0]}`, e); | ||
} | ||
try { | ||
const moddleRoot = await getModdleFromXML(diagramXML); | ||
const linter = new Linter({ | ||
resolver: nodeResolver | ||
}); | ||
const lintResults = await linter.lint(moddleRoot, parsedConfig); | ||
logReports(lintResults); | ||
} catch (e) { | ||
return logAndExit(e); | ||
} | ||
} | ||
if (configFlag) { | ||
fs.readFile(configFlag, "utf-8", (error, config) => { | ||
fs.readFile(configFlag, 'utf-8', (error, config) => { | ||
if (error) { | ||
console.log("Error: couldn't read specified config file."); | ||
process.exit(1); | ||
return logAndExit('Error: Could not read specified config file', error); | ||
} | ||
@@ -113,6 +139,5 @@ | ||
} else { | ||
fs.readFile(path.resolve(".bpmnlintrc"), "utf-8", (error, config) => { | ||
fs.readFile(path.resolve('.bpmnlintrc'), 'utf-8', (error, config) => { | ||
if (error) { | ||
console.log("Error: bpmnlint configuration file missing."); | ||
process.exit(1); | ||
return logAndExit('Error: Configuration file missing', error); | ||
} | ||
@@ -119,0 +144,0 @@ |
@@ -1,58 +0,243 @@ | ||
const testRule = require("./testRule"); | ||
const utils = require("./utils"); | ||
const testRule = require('./testRule'); | ||
const utils = require('./utils'); | ||
const flagsMap = { | ||
1: "warnings", | ||
2: "errors" | ||
1: 'warnings', | ||
2: 'errors', | ||
warn: 'warnings', | ||
error: 'errors' | ||
}; | ||
require("../rules/bpmnlint-label-required"); | ||
function Linter(options = {}) { | ||
const { | ||
resolver | ||
} = options; | ||
if (typeof resolver === 'undefined') { | ||
throw new Error('must provide <options.resolver>'); | ||
} | ||
this.resolver = resolver; | ||
this.cachedRules = {}; | ||
this.cachedConfigs = {}; | ||
} | ||
module.exports = Linter; | ||
/** | ||
* Applies a rule on the moddleRoot and adds reports to the finalReport | ||
* @param {*} moddleRoot | ||
* @param {*} ruleName | ||
* @param {*} ruleFlagIdx | ||
* | ||
* @param {ModdleElement} options.moddleRoot | ||
* @param {String|Number} options.ruleFlag | ||
* @param {Rule} options.rule | ||
* | ||
* @return {Object} lint results, keyed by category name | ||
*/ | ||
function applyRule({ moddleRoot, ruleFlagIdx, rule }) { | ||
const flagName = flagsMap[ruleFlagIdx]; | ||
let reports = []; | ||
Linter.prototype.applyRule = function applyRule({ moddleRoot, ruleFlag, rule, ruleConfig }) { | ||
if (ruleFlagIdx) { | ||
reports = testRule({ moddleRoot, rule }); | ||
if (typeof ruleFlag === 'string') { | ||
ruleFlag = ruleFlag.toLowerCase(); | ||
} | ||
const flagName = flagsMap[ruleFlag]; | ||
if (!flagName) { | ||
return {}; | ||
} | ||
const reports = testRule({ moddleRoot, rule, ruleConfig }); | ||
return { [flagName]: reports }; | ||
} | ||
}; | ||
module.exports = async function linter({ moddleRoot, config }) { | ||
// final report that holds all lint reports | ||
const finalReport = { warnings: [], errors: [] }; | ||
Object.entries(config).forEach(([ruleName, value]) => { | ||
let rule, ruleFlagIdx; | ||
if (typeof value === "object" && value !== null) { | ||
rule = require(value.path)(utils); | ||
ruleFlagIdx = value.flag; | ||
} else { | ||
try { | ||
rule = require(`../rules/bpmnlint-${ruleName}`)(utils); | ||
} catch (e) { | ||
try { | ||
rule = require(`../../bpmnlint-${ruleName}/index.js`)(utils); | ||
} catch (e) { | ||
console.error(`Couldn't find path to rule ${ruleName}.`); | ||
} | ||
} | ||
ruleFlagIdx = value; | ||
Linter.prototype.resolveRule = function(name) { | ||
const rule = this.cachedRules[name]; | ||
if (rule) { | ||
return Promise.resolve(rule); | ||
} | ||
return Promise.resolve(this.resolver.resolveRule(name)).then((ruleFactory) => { | ||
if (!ruleFactory) { | ||
throw new Error(`unknown rule <${name}>`); | ||
} | ||
const [flagName, reports] = Object.entries( | ||
applyRule({ moddleRoot, ruleFlagIdx, rule }) | ||
)[0]; | ||
const rule = this.cachedRules[name] = ruleFactory(utils); | ||
finalReport[flagName] = (finalReport[flagName] || []).concat(reports); | ||
return rule; | ||
}); | ||
}; | ||
return finalReport; | ||
Linter.prototype.resolveConfig = function(name) { | ||
const config = this.cachedConfigs[name]; | ||
if (config) { | ||
return Promise.resolve(config); | ||
} | ||
return Promise.resolve(this.resolver.resolveConfig(name)).then((config) => { | ||
if (!config) { | ||
throw new Error(`unknown config <${name}>`); | ||
} | ||
const actualConfig = this.cachedConfigs[name] = prefix(config, name); | ||
return actualConfig; | ||
}); | ||
}; | ||
/** | ||
* Take a linter config and return list of resolved rules. | ||
* | ||
* @param {Object} rulesConfig | ||
* | ||
* @return {Array<RuleDefinition>} | ||
*/ | ||
Linter.prototype.resolveRules = function(config) { | ||
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 | ||
}; | ||
}); | ||
}) | ||
); | ||
}); | ||
}; | ||
Linter.prototype.resolveConfiguredRules = function(config) { | ||
let parents = config.extends; | ||
if (typeof parents === 'string') { | ||
parents = [ parents ]; | ||
} | ||
if (typeof parents === 'undefined') { | ||
parents = []; | ||
} | ||
return Promise.all( | ||
parents.map((configName) => { | ||
return this.resolveConfig(configName).then((config) => { | ||
return this.resolveConfiguredRules(config); | ||
}); | ||
}) | ||
).then((inheritedRules) => { | ||
const rules = [ ...inheritedRules, config.rules || [] ].reduce((rules, currentRules) => { | ||
return { | ||
...rules, | ||
...currentRules | ||
}; | ||
}, {}); | ||
return rules; | ||
}); | ||
}; | ||
/** | ||
* Lint the given model root, using the specified linter config. | ||
* | ||
* @param {ModdleElement} moddleRoot | ||
* @param {Object} config rule config | ||
* | ||
* @return {Object} lint results, keyed by category names | ||
*/ | ||
Linter.prototype.lint = function(moddleRoot, config) { | ||
// load rules | ||
return this.resolveRules(config).then((ruleDefinitions) => { | ||
const finalReport = {}; | ||
ruleDefinitions.forEach(({ rule, name, value }) => { | ||
const { | ||
ruleFlag, | ||
ruleConfig | ||
} = this.parseRuleValue(value); | ||
const ruleResults = this.applyRule({ moddleRoot, ruleFlag, rule, ruleConfig }); | ||
Object.entries(ruleResults).forEach(([category, reports]) => { | ||
finalReport[category] = (finalReport[category] || []).concat(reports); | ||
}); | ||
}); | ||
return finalReport; | ||
}); | ||
}; | ||
Linter.prototype.parseRuleValue = function(value) { | ||
let ruleFlag; | ||
let ruleConfig; | ||
if (Array.isArray(value)) { | ||
ruleFlag = value[0]; | ||
ruleConfig = value[1]; | ||
} else { | ||
ruleFlag = value; | ||
ruleConfig = {}; | ||
} | ||
return { | ||
ruleConfig, | ||
ruleFlag | ||
}; | ||
}; | ||
// helpers /////////////////////////// | ||
function prefix(config, configName) { | ||
let pkg; | ||
if (configName.indexOf('bpmnlint:') === 0) { | ||
pkg = 'bpmnlint'; | ||
} else { | ||
pkg = configName.substring('plugin:'.length, configName.indexOf('/')); | ||
} | ||
const rules = config.rules || {}; | ||
const prefixedRules = Object.keys(rules).reduce((prefixed, name) => { | ||
const value = rules[name]; | ||
// prefix local rule definition | ||
if (name.indexOf('/') === -1) { | ||
name = `${pkg}/${name}`; | ||
} | ||
return { | ||
...prefixed, | ||
[name]: value | ||
}; | ||
}, {}); | ||
return { | ||
...config, | ||
rules: prefixedRules | ||
}; | ||
} |
@@ -1,2 +0,2 @@ | ||
const traverse = require("./traverse"); | ||
const traverse = require('./traverse'); | ||
@@ -3,0 +3,0 @@ class Reporter { |
@@ -12,3 +12,3 @@ /** | ||
var containedProperties = element.$descriptor.properties.filter(p => { | ||
return !p.isAttr && !p.isReference && p.type !== "String"; | ||
return !p.isAttr && !p.isReference && p.type !== 'String'; | ||
}); | ||
@@ -15,0 +15,0 @@ |
@@ -1,10 +0,19 @@ | ||
function isNodeOfType(node, type) { | ||
/** | ||
* Checks whether node is of specific bpmn type | ||
* @param {*} node | ||
* @param {*} type | ||
*/ | ||
return Boolean( | ||
node.$descriptor.allTypes.filter(({ name }) => `bpmn:${type}` === name) | ||
.length | ||
/** | ||
* Checks whether node is of specific bpmn type. | ||
* | ||
* @param {ModdleElement} node | ||
* @param {String} type | ||
* | ||
* @return {Boolean} | ||
*/ | ||
function is(node, type) { | ||
if (type.indexOf(':') === -1) { | ||
type = 'bpmn:' + type; | ||
} | ||
return ( | ||
(typeof node.$instanceOf === 'function') | ||
? node.$instanceOf(type) | ||
: node.$type === type | ||
); | ||
@@ -14,3 +23,3 @@ } | ||
module.exports = { | ||
isNodeOfType | ||
is | ||
}; |
{ | ||
"name": "bpmnlint", | ||
"version": "1.0.0-alpha4", | ||
"version": "1.0.0-alpha5", | ||
"main": "index.js", | ||
"repository": "https://github.com/siffogh/bpmnlint/", | ||
"keywords": [ | ||
"bpmnlint", | ||
"bpmn", | ||
"linter", | ||
"cli", | ||
"validation", | ||
"rules" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/bpmn-io/bpmnlint" | ||
}, | ||
"author": "Seif Eddine Ghezala <siffogh3@gmail.com>", | ||
"contributors": [ | ||
{ | ||
"name": "bpmn.io contributors", | ||
"url": "https://github.com/bpmn-io" | ||
} | ||
], | ||
"license": "MIT", | ||
"scripts": { | ||
"all": "run-s lint test", | ||
"lint": "eslint .", | ||
"dev": "npm test -- --watch", | ||
"test": "mocha -r esm --exclude 'test/integration/cli/**' 'test/**/*.js'" | ||
}, | ||
"bin": { | ||
@@ -12,3 +35,3 @@ "bpmnlint": "./bin/bpmnlint.js" | ||
"dependencies": { | ||
"bpmn-moddle": "4.0", | ||
"bpmn-moddle": "^4.0.0", | ||
"chalk": "^2.4.1", | ||
@@ -18,8 +41,11 @@ "meow": "^5.0.0" | ||
"devDependencies": { | ||
"chai": "^4.1.2", | ||
"eslint": "^5.3.0", | ||
"eslint-config-prettier": "^3.0.1", | ||
"eslint-plugin-bpmn-io": "^0.5.4", | ||
"eslint-plugin-prettier": "^2.6.2", | ||
"prettier": "^1.14.2" | ||
"esm": "^3.0.81", | ||
"execa": "^1.0.0", | ||
"install-local": "^0.6.0", | ||
"mocha": "^5.2.0", | ||
"npm-run-all": "^4.1.3" | ||
} | ||
} |
# bpmnlint | ||
Linter for BPMN diagrams. | ||
[![Build Status](https://travis-ci.org/bpmn-io/bpmnlint.svg?branch=master)](https://travis-ci.org/bpmn-io/bpmnlint) | ||
Validate and improve your BPMN diagrams. | ||
## Installing | ||
@@ -19,3 +23,3 @@ | ||
### As a command line tool | ||
#### Using a local **.bpmnlintrc** configuration file | ||
#### Using a local **.bpmnlintrc** configuration file | ||
- Make sure to have a **.bpmnlintrc** configuration file in the directory where you are running the tool: | ||
@@ -26,5 +30,7 @@ | ||
{ | ||
"label-required": 1, | ||
"start-event-required": 2, | ||
"end-event-required": 2 | ||
"rules": { | ||
"label-required": "warn", | ||
"start-event-required": "error", | ||
"end-event-required": "error" | ||
} | ||
} | ||
@@ -38,10 +44,12 @@ ``` | ||
#### Using an explicit configuration file | ||
- e.g. | ||
#### Using an explicit configuration file | ||
- e.g. | ||
```json | ||
// some-config.json file | ||
{ | ||
"label-required": 1, | ||
"start-event-required": 2, | ||
"end-event-required": 2 | ||
"rules": { | ||
"label-required": "warn", | ||
"start-event-required": "error", | ||
"end-event-required": "error" | ||
} | ||
} | ||
@@ -72,3 +80,3 @@ ``` | ||
### Implicit Configuration | ||
If the specified value for a rule is a number, it will hold the rule status flag: | ||
If the specified value for a rule is a number, it will hold the rule status flag: | ||
- 0: the rule is off | ||
@@ -80,7 +88,9 @@ - 1: problems reported by the rule are considered as warnings | ||
{ | ||
"label-required": 1 | ||
"rules": { | ||
"label-required": "warn" | ||
} | ||
} | ||
``` | ||
bpmnlint will then look for the rule first in the built-in rules. | ||
bpmnlint will then look for the rule first in the built-in rules. | ||
If not found, bpmnlint will look for the rule in the npm packages installed as **bpmn-**rule-name (e.g. bpmn-no-implicit-parallel-gateway). | ||
@@ -91,3 +101,3 @@ | ||
### Explicit Configuration | ||
If the specified value for a rule is an object, it will hold the following information: | ||
If the specified value for a rule is an object, it will hold the following information: | ||
- path to the the rule. | ||
@@ -98,5 +108,7 @@ - flag: rule status flag | ||
{ | ||
"bpmnlint-some-custom-rule": { | ||
"path": "some/local/path/bpmnlint-some-custom-rule", | ||
"flag": 2 | ||
"plugins": [ | ||
"custom-rules" | ||
], | ||
"rules": { | ||
"custom-rules/some-custom-rule": "error" | ||
} | ||
@@ -106,3 +118,3 @@ } | ||
### Adding Custom Rules | ||
### Adding Custom Rules | ||
> **Important:** The rule needs to have a suffix of 'bpmnlint-'. | ||
@@ -118,5 +130,7 @@ | ||
{ | ||
"bpmnlint-some-custom-rule": { | ||
"path": "some/local/path/bpmnlint-some-custom-rule", | ||
"flag": 2 | ||
"plugins": [ | ||
"custom-rules" | ||
], | ||
"rules": { | ||
"custom-rules/some-custom-rule": "error" | ||
} | ||
@@ -123,0 +137,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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
17
578
133
4
41760
8
2
Updatedbpmn-moddle@^4.0.0