diff --git a/README.md b/README.md index 0b26d3a..0488af7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,110 @@ -# mxlint rules + -TBD + + + + [![Contributors][contributors-shield]][contributors-url] + [![Forks][forks-shield]][forks-url] + [![Issues][issues-shield]][issues-url] + [![Unlicense License][license-shield]][license-url] + + + + +
+
+ + Logo + + +

MxLint - Rules

+ +

+ The repository of rules that come out-of-the-box rules with the MxLint CLI and Mendix Studio Pro extension +
+ MxLint website ยป +
+
+ View Demo + · + Report an issue + · + Request feature +

+
+ + +
+ Table of Contents +
    +
  1. Getting Started
  2. +
  3. Roadmap
  4. +
  5. Contributing
  6. +
  7. License
  8. +
  9. Contact
  10. +
+
+ + +## Getting started +Add new rules to your Mendix project by downloading the applicable `.rego` file and storing it in the project's `.mendix-cache/rules` folder. This folder contains subfolders for each rule category. + +Each rule comes with its own `_test` file, which contains test data and one or more test cases. + +For more information, see the installation instructions on the [MxLint website](https://mxlint.com/mendix-studio-pro-extension/installation/). + + + +## Roadmap + +- [x] Add README +- [ ] Add changelog +- [ ] Convert all [Mendix Best Practices for Development](https://docs.mendix.com/refguide/dev-best-practices/) to rules + + + +## Contributing +MxLint and its rules is a fully open source project, driven entirely by the Mendix community! That is why we welcome any and all contributions! + +If you want to contribute, this is the way: + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +You pull request will be reviewed and, if accepted, merged into the mainline of MxLint. + + + +## License +MxLint—the CLI tools, the Mendix Studio Pro extension and its rules—is distributed under the [AGPL license](https://github.com/mxlint/mxlint-rules/blob/main/LICENSE) + + + +## Contact +Xiwen Cheng - [LinkedIn](https://linkedin.com/in/xiwen) - [Email](mailto:x@cinaq.com) + +Bart Zantingh - [LinkedIn](https://linkedin.com/in/bartzantingh) - [Email](mailto:bart.zantingh@nl.abnamro.com) + +MxLint project home: [https://github.com/mxlint](https://github.com/mxlint) + +## Useful links +- [MxLint home](https://mxlint.com/) +- [Open Policy Agent home](https://www.openpolicyagent.org/) +- [Open Policy Agent docs](https://www.openpolicyagent.org/docs/latest/) +- [Rego language reference](https://www.openpolicyagent.org/docs/latest/policy-reference/) + +

(back to top)

+ + + +[contributors-shield]: https://img.shields.io/badge/Contributors-3-339933?style=for-the-badge&logo=data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KDTwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIFRyYW5zZm9ybWVkIGJ5OiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSJub25lIj4KDTxnIGlkPSJTVkdSZXBvX2JnQ2FycmllciIgc3Ryb2tlLXdpZHRoPSIwIi8+Cg08ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KDTxnIGlkPSJTVkdSZXBvX2ljb25DYXJyaWVyIj4gPGcgZmlsbD0iI2ZmZiI+IDxwYXRoIGQ9Ik0zLjQ2MiA3Ljk0MmE1LjYzOCA1LjYzOCAwIDAxMS4yMTYtMi4yOTljLS4xODctLjE0Ny0uOTI4LS43NTUtLjk2My0xLjIzMkMzLjY2MSAzLjYyOSA0LjQwMyAxIDQuNDAzIDFzLTIuMjQgMi44NzYtMi4zOTUgNC4wMzlDMS44ODQgNS45NSAzLjMgNy43NiAzLjQ0OSA3Ljk0NXYtLjAwM2guMDEzem05LjExNC0uMDQ3VjcuOWMwIC4wMDQuMDAzLjAwNy4wMDMuMDEuMjQ4LS4zMTQgMS41My0yIDEuNDEzLTIuODcyQzEzLjgzOCAzLjg3NiAxMS41OTggMSAxMS41OTggMXMuNzQyIDIuNjI5LjY4OCAzLjQxYy0uMDMyLjQ1OC0uNzA3IDEuMDMtLjkzMiAxLjIxYTUuNTcgNS41NyAwIDAxMS4yMjMgMi4yNzV6Ii8+IDxwYXRoIGQ9Ik0xMi41NzYgNy44OTh2LS4wMDZhNS42MTUgNS42MTUgMCAwMC0xLjIyMy0yLjI3NWMtLjg3LS45Ny0yLjA1Ni0xLjU1LTMuMzMzLTEuNTV2My4xMDZoLjAwNGMuMzEzLjAwNC41NjcuMjc0LjU2Ny42MDUgMCAuMDQtLjAwMy4wNzctLjAxLjExNGEuNTgzLjU4MyAwIDAxLS41NTcuNDloLS4wMXYxLjE1M2wtLjAwNiA1LjQ1OGguMTFzMS4yMDUtMS44MzcgMS44NTQtMi4zNjFjLjc2LS42MTUgMi42MDQtMS4zNzcgMi42MDQtMS4zNzd2LTMuMzJsLjAxLS4wMDNjLS4wMDQtLjAwNy0uMDA0LS4wMTctLjAwNy0uMDI0IDAtLjAwMyAwLS4wMDYtLjAwMy0uMDF6Ii8+IDxwYXRoIGQ9Ik04LjAxNCA5LjUzOFY4LjM4NmEuNTguNTggMCAwMS0uNTQ4LS40NDQuNjUuNjUgMCAwMS0uMDIyLS4xNmMwLS4zMzUuMjU3LS42MDUuNTczLS42MDVoLjAwNFY0LjA4Yy0xLjI4NCAwLTIuNDcyLjU4NS0zLjM0MyAxLjU2M2E1LjYxNiA1LjYxNiAwIDAwLTEuMjE2IDIuMjk5aC0uMDF2My4zNjRzMS44NDQuNzYxIDIuNjA0IDEuMzc2Yy42My41MSAxLjg0NyAyLjMxOCAxLjg0NyAyLjMxOGguMTE0di0uMDAzaC0uMDA2bC4wMDMtNS40NTl6Ii8+IDwvZz4gPC9nPgoNPC9zdmc+ +[contributors-url]: https://github.com/mxlint/mxlint-rules/graphs/contributors +[forks-shield]: https://img.shields.io/badge/Forks-3-007EC6?style=for-the-badge&logo=git&logoColor=white +[forks-url]: https://github.com/mxlint/mxlint-rules/network +[issues-shield]: https://img.shields.io/badge/Issues-2-DFB317?style=for-the-badge +[issues-url]: https://github.com/mxlint/mxlint-rules/issues +[license-shield]: https://img.shields.io/badge/License-AGPL-663066?style=for-the-badge&logo=gnu +[license-url]: https://github.com/mxlint/mxlint-rules/blob/main/LICENSE diff --git a/rules/005_microflows/005_0003_number_of_elements_in_microflow.rego b/rules/005_microflows/005_0003_number_of_elements_in_microflow.rego new file mode 100644 index 0000000..8d691c3 --- /dev/null +++ b/rules/005_microflows/005_0003_number_of_elements_in_microflow.rego @@ -0,0 +1,40 @@ +# METADATA +# scope: package +# title: No more than 25 elements in a microflow +# description: The bigger a microflow, the harder it will be to maintain. +# authors: +# - Bart Zantingh +# custom: +# category: Maintainability +# rulename: NumberOfElementsInMicroflow +# severity: MEDIUM +# rulenumber: 005_0003 +# remediation: Split microflow into logical, functional elements. +# input: "**/*$Microflow.yaml" +package app.mendix.microflows.number_of_elements_in_microflow + +import rego.v1 + +annotation := rego.metadata.chain()[1].annotations + +default allow := false + +allow if count(errors) == 0 + +errors contains error if { + name := input.Name + + count_elements := count(input.ObjectCollection.Objects) - 2 + count_elements > 25 + + error := sprintf( + "[%v, %v, %v] Microflow %v has %v actions which is more than 25", + [ + annotation.custom.severity, + annotation.custom.category, + annotation.custom.rulenumber, + name, + count_elements, + ], + ) +} diff --git a/rules/005_microflows/005_0003_number_of_elements_in_microflow_test.rego b/rules/005_microflows/005_0003_number_of_elements_in_microflow_test.rego new file mode 100644 index 0000000..a3a62a3 --- /dev/null +++ b/rules/005_microflows/005_0003_number_of_elements_in_microflow_test.rego @@ -0,0 +1,65 @@ +package app.mendix.microflows.number_of_elements_in_microflow_test + +import data.app.mendix.microflows.number_of_elements_in_microflow +import rego.v1 + +# Test data +microflow_with_less_than_25_activities := { + "$Type": "Microflows$Microflow", + "Name": "Microflow_with_less_than_25_activities", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$StartEvent"}, + {"$Type": "Microflows$EndEvent"}, + {"$Type": "Microflows$ActionActivity"}, + ], + }, +} + +microflow_with_more_than_25_activities := { + "$Type": "Microflows$Microflow", + "Name": "Microflow_with_more_than_25_activities", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$StartEvent"}, + {"$Type": "Microflows$EndEvent"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + ], + }, +} + +# Test cases +test_should_allow_when_microflow_has_less_than_25_activities if { + number_of_elements_in_microflow.allow with input as microflow_with_less_than_25_activities +} + +test_should_deny_when_microflow_has_more_than_25_activities if { + not number_of_elements_in_microflow.allow with input as microflow_with_more_than_25_activities +} diff --git a/rules/005_microflows/005_0004_complex_microflows_without_annotations.rego b/rules/005_microflows/005_0004_complex_microflows_without_annotations.rego new file mode 100644 index 0000000..ebed94d --- /dev/null +++ b/rules/005_microflows/005_0004_complex_microflows_without_annotations.rego @@ -0,0 +1,68 @@ +# METADATA +# scope: package +# title: Complex microflows without annotations +# description: Microflows with more than 10 actions and/or 2 decisions can be hard to understand. +# authors: +# - Bart Zantingh +# custom: +# category: Maintainability +# rulename: ComplexMicroflowsWithoutAnnotations +# severity: MEDIUM +# rulenumber: 005_0004 +# remediation: Add one or more annotations to explain the microflow. +# input: "**/*$Microflow.yaml" +package app.mendix.microflows.complex_microflows_without_annotations + +import rego.v1 + +annotation := rego.metadata.chain()[1].annotations + +default allow := false + +allow if count(errors) == 0 + +errors contains error if { + is_complex + + # if MF Is complex, collect all objects of type Microflows$Annotations from input file + annotation_collection := [annotation | + some annotation in input.ObjectCollection.Objects + annotation["$Type"] == "Microflows$Annotation" + ] + + count(annotation_collection) == 0 + + error := sprintf( + "[%v, %v, %v] Microflow %v has more than 10 activities and/or more than 2 exclusive splits, but no annotations", + [ + annotation.custom.severity, + annotation.custom.category, + annotation.custom.rulenumber, + input.Name, + ], + ) +} + +# A microflow is considered complex if one or more of these conditions are met: +# - it has more than 10 actions +# - it has more than 2 exclusive splits +# based on Mendix Best Practices for Development paragraph 4.3.2 +# see https://docs.mendix.com/refguide/dev-best-practices/#documentation-and-annotations + +is_complex if { + ex_splits_collection := [ex_split | + some ex_split in input.ObjectCollection.Objects + ex_split["$Type"] == "Microflows$ExclusiveSplit" + ] + + count(ex_splits_collection) > 2 +} + +is_complex if { + activity_collection := [activity | + some activity in input.ObjectCollection.Objects + activity["$Type"] == "Microflows$ActionActivity" + ] + + count(activity_collection) > 10 +} diff --git a/rules/005_microflows/005_0004_complex_microflows_without_annotations_test.rego b/rules/005_microflows/005_0004_complex_microflows_without_annotations_test.rego new file mode 100644 index 0000000..b4128ae --- /dev/null +++ b/rules/005_microflows/005_0004_complex_microflows_without_annotations_test.rego @@ -0,0 +1,115 @@ +package app.mendix.microflows.complex_microflows_without_annotations_test + +import data.app.mendix.microflows.complex_microflows_without_annotations +import rego.v1 + +# Test data +microflow_with_1_exclusive_split := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ExclusiveSplit"} + ], + }, +} + +microflow_with_2_actions_and_2_exclusive_splits := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$ExclusiveSplit"} + ], + }, +} + +microflow_with_11_actions_no_annotations := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"} + ], + }, +} + +microflow_with_11_actions_with_annotations := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$ActionActivity"}, + {"$Type": "Microflows$Annotation"} + ], + }, +} + +microflow_with_3_exclusive_splits_no_annotations := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$ExclusiveSplit"} + ], + }, +} + +microflow_with_3_exclusive_splits_with_annotations := { + "$Type": "Microflows$Microflow", + "Name": "Microflow", + "ObjectCollection": { + "$Type": "Microflows$MicroflowObjectCollection", + "Objects": [ + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$ExclusiveSplit"}, + {"$Type": "Microflows$Annotation"} + ], + }, +} + +test_should_allow_when_microflow_is_not_complex if { + complex_microflows_without_annotations.allow with input as microflow_with_2_actions_and_2_exclusive_splits + complex_microflows_without_annotations.allow with input as microflow_with_1_exclusive_split +} + +test_should_allow_when_microflow_is_complex_and_has_annotations if { + complex_microflows_without_annotations.allow with input as microflow_with_11_actions_with_annotations + complex_microflows_without_annotations.allow with input as microflow_with_3_exclusive_splits_with_annotations +} + +test_should_deny_when_microflow_is_complex_and_has_no_annotations if { + not complex_microflows_without_annotations.allow with input as microflow_with_11_actions_no_annotations + not complex_microflows_without_annotations.allow with input as microflow_with_3_exclusive_splits_no_annotations +}