Skip to content

Commit

Permalink
feat(cloud-templates): add linter plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
marstamm committed Jun 15, 2023
1 parent 09f7972 commit 7d131fd
Show file tree
Hide file tree
Showing 9 changed files with 834 additions and 52 deletions.
118 changes: 118 additions & 0 deletions src/cloud-element-templates/Linter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership.
*
* Camunda licenses this file to you under the MIT; you may not use this file
* except in compliance with the MIT License.
*/

import StaticResolver from 'bpmnlint/lib/resolver/static-resolver';
import ElementTemplates from './ElementTemplates';
import { getPropertyValue, validateProperty } from './util/propertyUtil';

import { applyConditions } from './Condition';

import BpmnModdle from 'bpmn-moddle';
import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe';

import { Validator } from './Validator';

export const elementTemplateLintRule = ({ templates = [] }) => {
const moddle = new BpmnModdle({ zeebe: zeebeModdle });

const validator = new Validator(moddle).addAll(templates);
const validTemplates = validator.getValidTemplates();

// We use the ElementTemplates Module without the required bpmn-js modules
// As we only use it to facilitate template ID and version lookup,
// access to commandstack etc. is not required
const elementTemplates = new ElementTemplates();
elementTemplates.set(validTemplates);

function check(node, reporter) {

let template = elementTemplates.get(node);

const templateId = elementTemplates._getTemplateId(node);

// Handle missing template
if (templateId && !template) {
reporter.report(
node.id,
'Linked element template not found',
{
name: node.name
}
);
return;
}

if (!template) {
return;
}

template = applyConditions(node, template);

// Check attributes
template.properties.forEach((property) => {
const value = getPropertyValue(node, property);
const error = validateProperty(value, property);

if (!error) {
return;
}

reporter.report(
node.id,
`${property.label} ${firstLetterToLowerCase(error)}`,
{
entryIds: [ getEntryId(property, template) ],
name: node.name
}
);
});
}

return {
check
};

};


export const ElementTemplateLinterPlugin = function(templates) {
return {
config: {
rules: {
'element-templates/validate': [ 'error', { templates } ]
}
},
resolver: new StaticResolver({
'rule:bpmnlint-plugin-element-templates/validate': elementTemplateLintRule
})
};
};


// helpers //////////////////////

function firstLetterToLowerCase(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
}

function getEntryId(property, template) {
const index = template.properties
.filter(p => p.group === property.group)
.indexOf(property);

const path = [ 'custom-entry', template.id ];

if (property.group) {
path.push(property.group);
}

path.push(index);
return path.join('-');
}
54 changes: 4 additions & 50 deletions src/cloud-element-templates/properties/CustomProperties.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import {
find,
forEach,
groupBy,
isString
groupBy
} from 'min-dash';

import { useService } from 'bpmn-js-properties-panel';

import { PropertyDescription } from '../../element-templates/components/PropertyDescription';

import { getPropertyValue, setPropertyValue } from '../util/propertyUtil';
import { getPropertyValue, setPropertyValue, validateProperty } from '../util/propertyUtil';

import {
Group,
Expand Down Expand Up @@ -426,61 +425,16 @@ function propertyGetter(element, property) {
}

function propertySetter(bpmnFactory, commandStack, element, property) {
return function getValue(value) {
return function setValue(value) {
return setPropertyValue(bpmnFactory, commandStack, element, property, value);
};
}

function propertyValidator(translate, property) {
return function validate(value) {
const { constraints = {} } = property;

const {
maxLength,
minLength,
notEmpty
} = constraints;

if (notEmpty && isEmpty(value)) {
return translate('Must not be empty.');
}

if (maxLength && (value || '').length > maxLength) {
return translate('Must have max length {maxLength}.', { maxLength });
}

if (minLength && (value || '').length < minLength) {
return translate('Must have min length {minLength}.', { minLength });
}

let { pattern } = constraints;

if (pattern) {
let message;

if (!isString(pattern)) {
message = pattern.message;
pattern = pattern.value;
}

if (!matchesPattern(value, pattern)) {
return message || translate('Must match pattern {pattern}.', { pattern });
}
}
};
return value => validateProperty(value, property, translate);
}

function isEmpty(value) {
if (typeof value === 'string') {
return !value.trim().length;
}

return value === undefined;
}

function matchesPattern(string, pattern) {
return new RegExp(pattern).test(string);
}

function groupByGroupId(properties) {
return groupBy(properties, 'group');
Expand Down
53 changes: 53 additions & 0 deletions src/cloud-element-templates/util/propertyUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import {
} from 'bpmn-js/lib/util/ModelUtil';

import {
default as defaultTranslate
} from 'diagram-js/lib/i18n/translate/translate';

import {
isString,
isUndefined, without
} from 'min-dash';

Expand Down Expand Up @@ -755,6 +760,42 @@ export function unsetProperty(commandStack, element, property) {
}
}

export function validateProperty(value, property, translate = defaultTranslate) {
const { constraints = {} } = property;

const {
maxLength,
minLength,
notEmpty
} = constraints;

if (notEmpty && isEmpty(value)) {
return translate('Must not be empty.');
}

if (maxLength && (value || '').length > maxLength) {
return translate('Must have max length {maxLength}.', { maxLength });
}

if (minLength && (value || '').length < minLength) {
return translate('Must have min length {minLength}.', { minLength });
}

let { pattern } = constraints;

if (pattern) {
let message;

if (!isString(pattern)) {
message = pattern.message;
pattern = pattern.value;
}

if (!matchesPattern(value, pattern)) {
return message || translate('Must match pattern {pattern}.', { pattern });
}
}
}

// helpers
function unknownBindingError(element, property) {
Expand All @@ -768,3 +809,15 @@ function unknownBindingError(element, property) {

return new Error(`unknown binding <${ type }> for element <${ id }>, this should never happen`);
}

function isEmpty(value) {
if (typeof value === 'string') {
return !value.trim().length;
}

return value === undefined;
}

function matchesPattern(string, pattern) {
return new RegExp(pattern).test(string);
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as ElementTemplatesPropertiesProviderModule } from './element-t

// utils
export { Validator as CloudElementTemplatesValidator } from './cloud-element-templates/Validator';
export { ElementTemplateLinterPlugin as CloudElementTemplatesLinterPlugin } from './cloud-element-templates/Linter';
57 changes: 56 additions & 1 deletion test/TestHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import Modeler from 'bpmn-js/lib/Modeler';

import axe from 'axe-core';

import BPMNModdle from 'bpmn-moddle';
import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe';

/**
* https://www.deque.com/axe/core-documentation/api-documentation/#axe-core-tags
*/
Expand Down Expand Up @@ -255,4 +258,56 @@ document.addEventListener('keydown', function(event) {
bpmnJS.saveXML({ format: true }).then(function(result) {
download(result.xml, 'test.bpmn', 'application/xml');
});
});
});

// Moddle helpers //////////////////////
export async function createModdle(xml) {
const moddle = new BPMNModdle({
zeebe: zeebeModdle
});

let root, warnings;

try {
({
rootElement: root,
warnings = []
} = await moddle.fromXML(xml, 'bpmn:Definitions', { lax: true }));
} catch (err) {
console.log(err);
}

return {
root,
moddle,
context: {
warnings
},
warnings
};
}

export function createDefinitions(xml = '') {
return `
<bpmn:definitions
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:modeler="http://camunda.org/schema/modeler/1.0"
xmlns:zeebe="http://camunda.org/schema/zeebe/1.0"
id="Definitions_1">
${ xml }
</bpmn:definitions>
`;
}


export function createProcess(bpmn = '', bpmndi = '') {
return createDefinitions(`
<bpmn:process id="Process_1" isExecutable="true">
${ bpmn }
</bpmn:process>
${ bpmndi }
`);
}
Loading

0 comments on commit 7d131fd

Please sign in to comment.