├── .gitattributes ├── .github └── workflows │ ├── ci.yaml │ └── update-docs.yaml ├── .gitignore ├── .markdownlint.yaml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── package-lock.json ├── package.json └── style-guide.md /.gitattributes: -------------------------------------------------------------------------------- 1 | Makefile linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: 18 13 | - run: npm install 14 | - run: make toc 15 | - run: git diff 16 | - run: | 17 | if [ -z "$(git status --porcelain)" ]; then 18 | echo "No changes" 19 | else 20 | echo "Please update the table of contents with make toc" 21 | exit 1 22 | fi 23 | - run: make markdownlint 24 | - run: git diff 25 | - run: | 26 | if [ -z "$(git status --porcelain)" ]; then 27 | echo "No changes" 28 | else 29 | echo "Please fix the markdownlint errors" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /.github/workflows/update-docs.yaml: -------------------------------------------------------------------------------- 1 | # this workflow is used to update the content at docs.styra.com 2 | # when it changes in this repo. 3 | name: Update Docs 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | jobs: 12 | update-docs: 13 | name: Update Docs 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Save version 22 | run: | 23 | mkdir -p versions 24 | echo "${{ github.sha }}" > versions/rego-style-guide 25 | 26 | - name: Update docs 27 | uses: leigholiver/commit-with-deploy-key@9562ffd1c0965c6d4f264e2555a569bd33ac7d05 28 | with: 29 | source: versions 30 | destination_folder: imported/versions 31 | destination_repo: StyraInc/docs 32 | deploy_key: ${{ secrets.STYRA_DOCS_DEPLOY_KEY }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | 3 | # MD013/line-length 4 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md013.md 5 | MD013: 6 | line_length: 120 7 | heading_line_length: 80 8 | code_blocks: false 9 | tables: false 10 | headings: true 11 | 12 | # MD024/no-duplicate-heading 13 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md024.md 14 | MD024: 15 | siblings_only: true 16 | 17 | # MD026/no-trailing-punctuation 18 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md026.md 19 | MD026: 20 | # Punctuation characters 21 | punctuation: ".,;:,;:!" 22 | 23 | # MD031/blanks-around-fences 24 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md031.md 25 | MD031: false 26 | 27 | # MD033/no-inline-html 28 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md033.md 29 | MD033: 30 | allowed_elements: 31 | - br 32 | - details 33 | - img 34 | - strong 35 | - summary 36 | 37 | # MD036/no-emphasis-as-heading 38 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md036.md 39 | MD036: false 40 | 41 | MD004: 42 | style: dash 43 | 44 | MD010: false 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | Note that since this is just a document of recommendations, semantic versioning is not used here. 5 | Versions are only used to keep track of changes from one version to another. 6 | 7 | ## [0.2.0] 8 | ### Added 9 | - Recommendation on using explicit imports for future keywords 10 | 11 | ### Changed 12 | - Updated all examples to make use of the new future keywords 13 | 14 | ## [0.1.0] 15 | ### Added 16 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHONY: deps 2 | deps: 3 | npm install 4 | 5 | PHONY: toc 6 | toc: deps 7 | npx markdown-toc -i style-guide.md --bullets="*" --maxdepth=3 8 | 9 | markdownlint: deps 10 | npx markdownlint-cli2 style-guide.md --config=.markdownlint.yaml 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rego Style Guide 2 | 3 | This repository contains source code for the 4 | [Rego Style Guide](https://docs.styra.com/opa/rego-style-guide). 5 | 6 | The purpose of this style guide is to provide a collection of recommendations 7 | and best practices for authoring 8 | [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/). 9 | From [Styra](https://www.styra.com), the founders of 10 | [Open Policy Agent](https://www.openpolicyagent.org) (OPA), 11 | and some of the most experienced members of the community, 12 | we hope to share lessons learnt from authoring and reviewing hundreds of 13 | thousands of lines of Rego over the years. 14 | 15 | > [!NOTE] 16 | > You might also be interested in checking out [Regal](https://docs.styra.com/regal), 17 | > the new linter for Rego from Styra. 18 | 19 | If you are interested in suggesting changes to the guide's content, 20 | please feel free to raise an issue or PR on this repo. The document 21 | containing the guide's content can be found in `style-guide.md`. 22 | 23 | For questions, discussions and announcements related to Styra products, 24 | services and open source projects, please join the 25 | [Styra community on Slack](https://inviter.co/styra)! 26 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rego-style-guide", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "markdown-toc": "^1.2.0", 9 | "markdownlint-cli2": "^0.13.0" 10 | } 11 | }, 12 | "node_modules/@nodelib/fs.scandir": { 13 | "version": "2.1.5", 14 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 15 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 16 | "dependencies": { 17 | "@nodelib/fs.stat": "2.0.5", 18 | "run-parallel": "^1.1.9" 19 | }, 20 | "engines": { 21 | "node": ">= 8" 22 | } 23 | }, 24 | "node_modules/@nodelib/fs.stat": { 25 | "version": "2.0.5", 26 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 27 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 28 | "engines": { 29 | "node": ">= 8" 30 | } 31 | }, 32 | "node_modules/@nodelib/fs.walk": { 33 | "version": "1.2.8", 34 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 35 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 36 | "dependencies": { 37 | "@nodelib/fs.scandir": "2.1.5", 38 | "fastq": "^1.6.0" 39 | }, 40 | "engines": { 41 | "node": ">= 8" 42 | } 43 | }, 44 | "node_modules/@sindresorhus/merge-streams": { 45 | "version": "2.3.0", 46 | "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", 47 | "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", 48 | "engines": { 49 | "node": ">=18" 50 | }, 51 | "funding": { 52 | "url": "https://github.com/sponsors/sindresorhus" 53 | } 54 | }, 55 | "node_modules/ansi-red": { 56 | "version": "0.1.1", 57 | "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", 58 | "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", 59 | "dependencies": { 60 | "ansi-wrap": "0.1.0" 61 | }, 62 | "engines": { 63 | "node": ">=0.10.0" 64 | } 65 | }, 66 | "node_modules/ansi-wrap": { 67 | "version": "0.1.0", 68 | "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", 69 | "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", 70 | "engines": { 71 | "node": ">=0.10.0" 72 | } 73 | }, 74 | "node_modules/argparse": { 75 | "version": "1.0.10", 76 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 77 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 78 | "dependencies": { 79 | "sprintf-js": "~1.0.2" 80 | } 81 | }, 82 | "node_modules/autolinker": { 83 | "version": "0.28.1", 84 | "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz", 85 | "integrity": "sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ==", 86 | "dependencies": { 87 | "gulp-header": "^1.7.1" 88 | } 89 | }, 90 | "node_modules/braces": { 91 | "version": "3.0.3", 92 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 93 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 94 | "dependencies": { 95 | "fill-range": "^7.1.1" 96 | }, 97 | "engines": { 98 | "node": ">=8" 99 | } 100 | }, 101 | "node_modules/braces/node_modules/fill-range": { 102 | "version": "7.1.1", 103 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 104 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 105 | "dependencies": { 106 | "to-regex-range": "^5.0.1" 107 | }, 108 | "engines": { 109 | "node": ">=8" 110 | } 111 | }, 112 | "node_modules/buffer-from": { 113 | "version": "1.1.2", 114 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 115 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 116 | }, 117 | "node_modules/coffee-script": { 118 | "version": "1.12.7", 119 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", 120 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", 121 | "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", 122 | "bin": { 123 | "cake": "bin/cake", 124 | "coffee": "bin/coffee" 125 | }, 126 | "engines": { 127 | "node": ">=0.8.0" 128 | } 129 | }, 130 | "node_modules/concat-stream": { 131 | "version": "1.6.2", 132 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 133 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 134 | "engines": [ 135 | "node >= 0.8" 136 | ], 137 | "dependencies": { 138 | "buffer-from": "^1.0.0", 139 | "inherits": "^2.0.3", 140 | "readable-stream": "^2.2.2", 141 | "typedarray": "^0.0.6" 142 | } 143 | }, 144 | "node_modules/concat-with-sourcemaps": { 145 | "version": "1.1.0", 146 | "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", 147 | "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", 148 | "dependencies": { 149 | "source-map": "^0.6.1" 150 | } 151 | }, 152 | "node_modules/core-util-is": { 153 | "version": "1.0.3", 154 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 155 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 156 | }, 157 | "node_modules/diacritics-map": { 158 | "version": "0.1.0", 159 | "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", 160 | "integrity": "sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==", 161 | "engines": { 162 | "node": ">=0.8.0" 163 | } 164 | }, 165 | "node_modules/entities": { 166 | "version": "4.5.0", 167 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 168 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 169 | "engines": { 170 | "node": ">=0.12" 171 | }, 172 | "funding": { 173 | "url": "https://github.com/fb55/entities?sponsor=1" 174 | } 175 | }, 176 | "node_modules/esprima": { 177 | "version": "4.0.1", 178 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 179 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 180 | "bin": { 181 | "esparse": "bin/esparse.js", 182 | "esvalidate": "bin/esvalidate.js" 183 | }, 184 | "engines": { 185 | "node": ">=4" 186 | } 187 | }, 188 | "node_modules/expand-range": { 189 | "version": "1.8.2", 190 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", 191 | "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", 192 | "dependencies": { 193 | "fill-range": "^2.1.0" 194 | }, 195 | "engines": { 196 | "node": ">=0.10.0" 197 | } 198 | }, 199 | "node_modules/extend-shallow": { 200 | "version": "2.0.1", 201 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 202 | "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", 203 | "dependencies": { 204 | "is-extendable": "^0.1.0" 205 | }, 206 | "engines": { 207 | "node": ">=0.10.0" 208 | } 209 | }, 210 | "node_modules/fast-glob": { 211 | "version": "3.3.2", 212 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 213 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 214 | "dependencies": { 215 | "@nodelib/fs.stat": "^2.0.2", 216 | "@nodelib/fs.walk": "^1.2.3", 217 | "glob-parent": "^5.1.2", 218 | "merge2": "^1.3.0", 219 | "micromatch": "^4.0.4" 220 | }, 221 | "engines": { 222 | "node": ">=8.6.0" 223 | } 224 | }, 225 | "node_modules/fastq": { 226 | "version": "1.17.1", 227 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 228 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 229 | "dependencies": { 230 | "reusify": "^1.0.4" 231 | } 232 | }, 233 | "node_modules/fill-range": { 234 | "version": "2.2.4", 235 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", 236 | "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", 237 | "dependencies": { 238 | "is-number": "^2.1.0", 239 | "isobject": "^2.0.0", 240 | "randomatic": "^3.0.0", 241 | "repeat-element": "^1.1.2", 242 | "repeat-string": "^1.5.2" 243 | }, 244 | "engines": { 245 | "node": ">=0.10.0" 246 | } 247 | }, 248 | "node_modules/for-in": { 249 | "version": "1.0.2", 250 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 251 | "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", 252 | "engines": { 253 | "node": ">=0.10.0" 254 | } 255 | }, 256 | "node_modules/glob-parent": { 257 | "version": "5.1.2", 258 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 259 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 260 | "dependencies": { 261 | "is-glob": "^4.0.1" 262 | }, 263 | "engines": { 264 | "node": ">= 6" 265 | } 266 | }, 267 | "node_modules/globby": { 268 | "version": "14.0.1", 269 | "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", 270 | "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", 271 | "dependencies": { 272 | "@sindresorhus/merge-streams": "^2.1.0", 273 | "fast-glob": "^3.3.2", 274 | "ignore": "^5.2.4", 275 | "path-type": "^5.0.0", 276 | "slash": "^5.1.0", 277 | "unicorn-magic": "^0.1.0" 278 | }, 279 | "engines": { 280 | "node": ">=18" 281 | }, 282 | "funding": { 283 | "url": "https://github.com/sponsors/sindresorhus" 284 | } 285 | }, 286 | "node_modules/gray-matter": { 287 | "version": "2.1.1", 288 | "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", 289 | "integrity": "sha512-vbmvP1Fe/fxuT2QuLVcqb2BfK7upGhhbLIt9/owWEvPYrZZEkelLcq2HqzxosV+PQ67dUFLaAeNpH7C4hhICAA==", 290 | "dependencies": { 291 | "ansi-red": "^0.1.1", 292 | "coffee-script": "^1.12.4", 293 | "extend-shallow": "^2.0.1", 294 | "js-yaml": "^3.8.1", 295 | "toml": "^2.3.2" 296 | }, 297 | "engines": { 298 | "node": ">=0.10.0" 299 | } 300 | }, 301 | "node_modules/gulp-header": { 302 | "version": "1.8.12", 303 | "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", 304 | "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", 305 | "deprecated": "Removed event-stream from gulp-header", 306 | "dependencies": { 307 | "concat-with-sourcemaps": "*", 308 | "lodash.template": "^4.4.0", 309 | "through2": "^2.0.0" 310 | } 311 | }, 312 | "node_modules/ignore": { 313 | "version": "5.3.2", 314 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 315 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 316 | "engines": { 317 | "node": ">= 4" 318 | } 319 | }, 320 | "node_modules/inherits": { 321 | "version": "2.0.4", 322 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 323 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 324 | }, 325 | "node_modules/is-buffer": { 326 | "version": "1.1.6", 327 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 328 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 329 | }, 330 | "node_modules/is-extendable": { 331 | "version": "0.1.1", 332 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 333 | "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", 334 | "engines": { 335 | "node": ">=0.10.0" 336 | } 337 | }, 338 | "node_modules/is-extglob": { 339 | "version": "2.1.1", 340 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 341 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 342 | "engines": { 343 | "node": ">=0.10.0" 344 | } 345 | }, 346 | "node_modules/is-glob": { 347 | "version": "4.0.3", 348 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 349 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 350 | "dependencies": { 351 | "is-extglob": "^2.1.1" 352 | }, 353 | "engines": { 354 | "node": ">=0.10.0" 355 | } 356 | }, 357 | "node_modules/is-number": { 358 | "version": "2.1.0", 359 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", 360 | "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", 361 | "dependencies": { 362 | "kind-of": "^3.0.2" 363 | }, 364 | "engines": { 365 | "node": ">=0.10.0" 366 | } 367 | }, 368 | "node_modules/is-plain-object": { 369 | "version": "2.0.4", 370 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 371 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 372 | "dependencies": { 373 | "isobject": "^3.0.1" 374 | }, 375 | "engines": { 376 | "node": ">=0.10.0" 377 | } 378 | }, 379 | "node_modules/is-plain-object/node_modules/isobject": { 380 | "version": "3.0.1", 381 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 382 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", 383 | "engines": { 384 | "node": ">=0.10.0" 385 | } 386 | }, 387 | "node_modules/isarray": { 388 | "version": "1.0.0", 389 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 390 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 391 | }, 392 | "node_modules/isobject": { 393 | "version": "2.1.0", 394 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", 395 | "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", 396 | "dependencies": { 397 | "isarray": "1.0.0" 398 | }, 399 | "engines": { 400 | "node": ">=0.10.0" 401 | } 402 | }, 403 | "node_modules/js-yaml": { 404 | "version": "3.14.1", 405 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 406 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 407 | "dependencies": { 408 | "argparse": "^1.0.7", 409 | "esprima": "^4.0.0" 410 | }, 411 | "bin": { 412 | "js-yaml": "bin/js-yaml.js" 413 | } 414 | }, 415 | "node_modules/jsonc-parser": { 416 | "version": "3.2.1", 417 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", 418 | "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" 419 | }, 420 | "node_modules/kind-of": { 421 | "version": "3.2.2", 422 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 423 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", 424 | "dependencies": { 425 | "is-buffer": "^1.1.5" 426 | }, 427 | "engines": { 428 | "node": ">=0.10.0" 429 | } 430 | }, 431 | "node_modules/lazy-cache": { 432 | "version": "2.0.2", 433 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", 434 | "integrity": "sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA==", 435 | "dependencies": { 436 | "set-getter": "^0.1.0" 437 | }, 438 | "engines": { 439 | "node": ">=0.10.0" 440 | } 441 | }, 442 | "node_modules/linkify-it": { 443 | "version": "5.0.0", 444 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", 445 | "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", 446 | "dependencies": { 447 | "uc.micro": "^2.0.0" 448 | } 449 | }, 450 | "node_modules/list-item": { 451 | "version": "1.1.1", 452 | "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", 453 | "integrity": "sha512-S3D0WZ4J6hyM8o5SNKWaMYB1ALSacPZ2nHGEuCjmHZ+dc03gFeNZoNDcqfcnO4vDhTZmNrqrpYZCdXsRh22bzw==", 454 | "dependencies": { 455 | "expand-range": "^1.8.1", 456 | "extend-shallow": "^2.0.1", 457 | "is-number": "^2.1.0", 458 | "repeat-string": "^1.5.2" 459 | }, 460 | "engines": { 461 | "node": ">=0.10.0" 462 | } 463 | }, 464 | "node_modules/lodash._reinterpolate": { 465 | "version": "3.0.0", 466 | "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", 467 | "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" 468 | }, 469 | "node_modules/lodash.template": { 470 | "version": "4.5.0", 471 | "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", 472 | "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", 473 | "dependencies": { 474 | "lodash._reinterpolate": "^3.0.0", 475 | "lodash.templatesettings": "^4.0.0" 476 | } 477 | }, 478 | "node_modules/lodash.templatesettings": { 479 | "version": "4.2.0", 480 | "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", 481 | "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", 482 | "dependencies": { 483 | "lodash._reinterpolate": "^3.0.0" 484 | } 485 | }, 486 | "node_modules/markdown-it": { 487 | "version": "14.1.0", 488 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", 489 | "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", 490 | "dependencies": { 491 | "argparse": "^2.0.1", 492 | "entities": "^4.4.0", 493 | "linkify-it": "^5.0.0", 494 | "mdurl": "^2.0.0", 495 | "punycode.js": "^2.3.1", 496 | "uc.micro": "^2.1.0" 497 | }, 498 | "bin": { 499 | "markdown-it": "bin/markdown-it.mjs" 500 | } 501 | }, 502 | "node_modules/markdown-it/node_modules/argparse": { 503 | "version": "2.0.1", 504 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 505 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 506 | }, 507 | "node_modules/markdown-link": { 508 | "version": "0.1.1", 509 | "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", 510 | "integrity": "sha512-TurLymbyLyo+kAUUAV9ggR9EPcDjP/ctlv9QAFiqUH7c+t6FlsbivPo9OKTU8xdOx9oNd2drW/Fi5RRElQbUqA==", 511 | "engines": { 512 | "node": ">=0.10.0" 513 | } 514 | }, 515 | "node_modules/markdown-toc": { 516 | "version": "1.2.0", 517 | "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz", 518 | "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==", 519 | "dependencies": { 520 | "concat-stream": "^1.5.2", 521 | "diacritics-map": "^0.1.0", 522 | "gray-matter": "^2.1.0", 523 | "lazy-cache": "^2.0.2", 524 | "list-item": "^1.1.1", 525 | "markdown-link": "^0.1.1", 526 | "minimist": "^1.2.0", 527 | "mixin-deep": "^1.1.3", 528 | "object.pick": "^1.2.0", 529 | "remarkable": "^1.7.1", 530 | "repeat-string": "^1.6.1", 531 | "strip-color": "^0.1.0" 532 | }, 533 | "bin": { 534 | "markdown-toc": "cli.js" 535 | }, 536 | "engines": { 537 | "node": ">=0.10.0" 538 | } 539 | }, 540 | "node_modules/markdownlint": { 541 | "version": "0.34.0", 542 | "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz", 543 | "integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==", 544 | "dependencies": { 545 | "markdown-it": "14.1.0", 546 | "markdownlint-micromark": "0.1.9" 547 | }, 548 | "engines": { 549 | "node": ">=18" 550 | }, 551 | "funding": { 552 | "url": "https://github.com/sponsors/DavidAnson" 553 | } 554 | }, 555 | "node_modules/markdownlint-cli2": { 556 | "version": "0.13.0", 557 | "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.13.0.tgz", 558 | "integrity": "sha512-Pg4nF7HlopU97ZXtrcVISWp3bdsuc5M0zXyLp2/sJv2zEMlInrau0ZKK482fQURzVezJzWBpNmu4u6vGAhij+g==", 559 | "dependencies": { 560 | "globby": "14.0.1", 561 | "js-yaml": "4.1.0", 562 | "jsonc-parser": "3.2.1", 563 | "markdownlint": "0.34.0", 564 | "markdownlint-cli2-formatter-default": "0.0.4", 565 | "micromatch": "4.0.5" 566 | }, 567 | "bin": { 568 | "markdownlint-cli2": "markdownlint-cli2.js" 569 | }, 570 | "engines": { 571 | "node": ">=18" 572 | }, 573 | "funding": { 574 | "url": "https://github.com/sponsors/DavidAnson" 575 | } 576 | }, 577 | "node_modules/markdownlint-cli2-formatter-default": { 578 | "version": "0.0.4", 579 | "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.4.tgz", 580 | "integrity": "sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==", 581 | "peerDependencies": { 582 | "markdownlint-cli2": ">=0.0.4" 583 | } 584 | }, 585 | "node_modules/markdownlint-cli2/node_modules/argparse": { 586 | "version": "2.0.1", 587 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 588 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 589 | }, 590 | "node_modules/markdownlint-cli2/node_modules/js-yaml": { 591 | "version": "4.1.0", 592 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 593 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 594 | "dependencies": { 595 | "argparse": "^2.0.1" 596 | }, 597 | "bin": { 598 | "js-yaml": "bin/js-yaml.js" 599 | } 600 | }, 601 | "node_modules/markdownlint-micromark": { 602 | "version": "0.1.9", 603 | "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz", 604 | "integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==", 605 | "engines": { 606 | "node": ">=18" 607 | }, 608 | "funding": { 609 | "url": "https://github.com/sponsors/DavidAnson" 610 | } 611 | }, 612 | "node_modules/math-random": { 613 | "version": "1.0.4", 614 | "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", 615 | "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" 616 | }, 617 | "node_modules/mdurl": { 618 | "version": "2.0.0", 619 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", 620 | "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" 621 | }, 622 | "node_modules/merge2": { 623 | "version": "1.4.1", 624 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 625 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 626 | "engines": { 627 | "node": ">= 8" 628 | } 629 | }, 630 | "node_modules/micromatch": { 631 | "version": "4.0.5", 632 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 633 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 634 | "dependencies": { 635 | "braces": "^3.0.2", 636 | "picomatch": "^2.3.1" 637 | }, 638 | "engines": { 639 | "node": ">=8.6" 640 | } 641 | }, 642 | "node_modules/minimist": { 643 | "version": "1.2.8", 644 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 645 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 646 | "funding": { 647 | "url": "https://github.com/sponsors/ljharb" 648 | } 649 | }, 650 | "node_modules/mixin-deep": { 651 | "version": "1.3.2", 652 | "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", 653 | "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", 654 | "dependencies": { 655 | "for-in": "^1.0.2", 656 | "is-extendable": "^1.0.1" 657 | }, 658 | "engines": { 659 | "node": ">=0.10.0" 660 | } 661 | }, 662 | "node_modules/mixin-deep/node_modules/is-extendable": { 663 | "version": "1.0.1", 664 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", 665 | "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", 666 | "dependencies": { 667 | "is-plain-object": "^2.0.4" 668 | }, 669 | "engines": { 670 | "node": ">=0.10.0" 671 | } 672 | }, 673 | "node_modules/object.pick": { 674 | "version": "1.3.0", 675 | "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", 676 | "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", 677 | "dependencies": { 678 | "isobject": "^3.0.1" 679 | }, 680 | "engines": { 681 | "node": ">=0.10.0" 682 | } 683 | }, 684 | "node_modules/object.pick/node_modules/isobject": { 685 | "version": "3.0.1", 686 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 687 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", 688 | "engines": { 689 | "node": ">=0.10.0" 690 | } 691 | }, 692 | "node_modules/path-type": { 693 | "version": "5.0.0", 694 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", 695 | "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", 696 | "engines": { 697 | "node": ">=12" 698 | }, 699 | "funding": { 700 | "url": "https://github.com/sponsors/sindresorhus" 701 | } 702 | }, 703 | "node_modules/picomatch": { 704 | "version": "2.3.1", 705 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 706 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 707 | "engines": { 708 | "node": ">=8.6" 709 | }, 710 | "funding": { 711 | "url": "https://github.com/sponsors/jonschlinkert" 712 | } 713 | }, 714 | "node_modules/process-nextick-args": { 715 | "version": "2.0.1", 716 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 717 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 718 | }, 719 | "node_modules/punycode.js": { 720 | "version": "2.3.1", 721 | "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", 722 | "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", 723 | "engines": { 724 | "node": ">=6" 725 | } 726 | }, 727 | "node_modules/queue-microtask": { 728 | "version": "1.2.3", 729 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 730 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 731 | "funding": [ 732 | { 733 | "type": "github", 734 | "url": "https://github.com/sponsors/feross" 735 | }, 736 | { 737 | "type": "patreon", 738 | "url": "https://www.patreon.com/feross" 739 | }, 740 | { 741 | "type": "consulting", 742 | "url": "https://feross.org/support" 743 | } 744 | ] 745 | }, 746 | "node_modules/randomatic": { 747 | "version": "3.1.1", 748 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", 749 | "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", 750 | "dependencies": { 751 | "is-number": "^4.0.0", 752 | "kind-of": "^6.0.0", 753 | "math-random": "^1.0.1" 754 | }, 755 | "engines": { 756 | "node": ">= 0.10.0" 757 | } 758 | }, 759 | "node_modules/randomatic/node_modules/is-number": { 760 | "version": "4.0.0", 761 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", 762 | "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", 763 | "engines": { 764 | "node": ">=0.10.0" 765 | } 766 | }, 767 | "node_modules/randomatic/node_modules/kind-of": { 768 | "version": "6.0.3", 769 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 770 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 771 | "engines": { 772 | "node": ">=0.10.0" 773 | } 774 | }, 775 | "node_modules/readable-stream": { 776 | "version": "2.3.8", 777 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 778 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 779 | "dependencies": { 780 | "core-util-is": "~1.0.0", 781 | "inherits": "~2.0.3", 782 | "isarray": "~1.0.0", 783 | "process-nextick-args": "~2.0.0", 784 | "safe-buffer": "~5.1.1", 785 | "string_decoder": "~1.1.1", 786 | "util-deprecate": "~1.0.1" 787 | } 788 | }, 789 | "node_modules/remarkable": { 790 | "version": "1.7.4", 791 | "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", 792 | "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", 793 | "dependencies": { 794 | "argparse": "^1.0.10", 795 | "autolinker": "~0.28.0" 796 | }, 797 | "bin": { 798 | "remarkable": "bin/remarkable.js" 799 | }, 800 | "engines": { 801 | "node": ">= 0.10.0" 802 | } 803 | }, 804 | "node_modules/repeat-element": { 805 | "version": "1.1.4", 806 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", 807 | "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", 808 | "engines": { 809 | "node": ">=0.10.0" 810 | } 811 | }, 812 | "node_modules/repeat-string": { 813 | "version": "1.6.1", 814 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 815 | "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", 816 | "engines": { 817 | "node": ">=0.10" 818 | } 819 | }, 820 | "node_modules/reusify": { 821 | "version": "1.0.4", 822 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 823 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 824 | "engines": { 825 | "iojs": ">=1.0.0", 826 | "node": ">=0.10.0" 827 | } 828 | }, 829 | "node_modules/run-parallel": { 830 | "version": "1.2.0", 831 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 832 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 833 | "funding": [ 834 | { 835 | "type": "github", 836 | "url": "https://github.com/sponsors/feross" 837 | }, 838 | { 839 | "type": "patreon", 840 | "url": "https://www.patreon.com/feross" 841 | }, 842 | { 843 | "type": "consulting", 844 | "url": "https://feross.org/support" 845 | } 846 | ], 847 | "dependencies": { 848 | "queue-microtask": "^1.2.2" 849 | } 850 | }, 851 | "node_modules/safe-buffer": { 852 | "version": "5.1.2", 853 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 854 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 855 | }, 856 | "node_modules/set-getter": { 857 | "version": "0.1.1", 858 | "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz", 859 | "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==", 860 | "dependencies": { 861 | "to-object-path": "^0.3.0" 862 | }, 863 | "engines": { 864 | "node": ">=0.10.0" 865 | } 866 | }, 867 | "node_modules/slash": { 868 | "version": "5.1.0", 869 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", 870 | "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", 871 | "engines": { 872 | "node": ">=14.16" 873 | }, 874 | "funding": { 875 | "url": "https://github.com/sponsors/sindresorhus" 876 | } 877 | }, 878 | "node_modules/source-map": { 879 | "version": "0.6.1", 880 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 881 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 882 | "engines": { 883 | "node": ">=0.10.0" 884 | } 885 | }, 886 | "node_modules/sprintf-js": { 887 | "version": "1.0.3", 888 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 889 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" 890 | }, 891 | "node_modules/string_decoder": { 892 | "version": "1.1.1", 893 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 894 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 895 | "dependencies": { 896 | "safe-buffer": "~5.1.0" 897 | } 898 | }, 899 | "node_modules/strip-color": { 900 | "version": "0.1.0", 901 | "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", 902 | "integrity": "sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==", 903 | "engines": { 904 | "node": ">=0.10.0" 905 | } 906 | }, 907 | "node_modules/through2": { 908 | "version": "2.0.5", 909 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 910 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 911 | "dependencies": { 912 | "readable-stream": "~2.3.6", 913 | "xtend": "~4.0.1" 914 | } 915 | }, 916 | "node_modules/to-object-path": { 917 | "version": "0.3.0", 918 | "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", 919 | "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", 920 | "dependencies": { 921 | "kind-of": "^3.0.2" 922 | }, 923 | "engines": { 924 | "node": ">=0.10.0" 925 | } 926 | }, 927 | "node_modules/to-regex-range": { 928 | "version": "5.0.1", 929 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 930 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 931 | "dependencies": { 932 | "is-number": "^7.0.0" 933 | }, 934 | "engines": { 935 | "node": ">=8.0" 936 | } 937 | }, 938 | "node_modules/to-regex-range/node_modules/is-number": { 939 | "version": "7.0.0", 940 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 941 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 942 | "engines": { 943 | "node": ">=0.12.0" 944 | } 945 | }, 946 | "node_modules/toml": { 947 | "version": "2.3.6", 948 | "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", 949 | "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==" 950 | }, 951 | "node_modules/typedarray": { 952 | "version": "0.0.6", 953 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 954 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 955 | }, 956 | "node_modules/uc.micro": { 957 | "version": "2.1.0", 958 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", 959 | "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" 960 | }, 961 | "node_modules/unicorn-magic": { 962 | "version": "0.1.0", 963 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", 964 | "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", 965 | "engines": { 966 | "node": ">=18" 967 | }, 968 | "funding": { 969 | "url": "https://github.com/sponsors/sindresorhus" 970 | } 971 | }, 972 | "node_modules/util-deprecate": { 973 | "version": "1.0.2", 974 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 975 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 976 | }, 977 | "node_modules/xtend": { 978 | "version": "4.0.2", 979 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 980 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 981 | "engines": { 982 | "node": ">=0.4" 983 | } 984 | } 985 | } 986 | } 987 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "markdown-toc": "^1.2.0", 4 | "markdownlint-cli2": "^0.13.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /style-guide.md: -------------------------------------------------------------------------------- 1 | # Rego Style Guide 2 | 3 | 4 | 5 | The purpose of this style guide is to provide a collection of recommendations and best practices for authoring 6 | [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/). 7 | From [Styra](https://www.styra.com), the founders of [Open Policy Agent](https://www.openpolicyagent.org) (OPA), 8 | and some of the most experienced members of the community, 9 | we hope to share lessons learnt from authoring and reviewing hundreds of thousands of lines of Rego over the years. 10 | 11 | With new features, language constructs, and other improvements continuously finding their way into OPA, we aim to keep 12 | this style guide a reflection of what we consider current best practices. Make sure to check back every once in a while, 13 | and see the changelog for updates since your last visit. 14 | 15 | ## Regal 16 | 17 | Inspired by this style guide, [Regal](https://github.com/StyraInc/regal) is a new linter for Rego that allows you 18 | to enforce many of the recommendations in this guide, as well as identifying issues, bugs and potential problems in your 19 | Rego policies. If you enjoy this style guide, make sure to check it out! 20 | 21 | ## Contents 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | * [General Advice](#general-advice) 30 | * [Optimize for readability, not performance](#optimize-for-readability-not-performance) 31 | * [Use `opa fmt`](#use-opa-fmt) 32 | * [Use strict mode](#use-strict-mode) 33 | * [Use metadata annotations](#use-metadata-annotations) 34 | * [Get to know the built-in functions](#get-to-know-the-built-in-functions) 35 | * [Consider using JSON schemas for type checking](#consider-using-json-schemas-for-type-checking) 36 | * [Style](#style) 37 | * [Prefer snake_case for rule names and variables](#prefer-snake_case-for-rule-names-and-variables) 38 | * [Optionally, use leading underscore for rules intended for internal use](#optionally-use-leading-underscore-for-rules-intended-for-internal-use) 39 | * [Keep line length `<=` 120 characters](#keep-line-length--120-characters) 40 | * [Rules](#rules) 41 | * [Use helper rules and functions](#use-helper-rules-and-functions) 42 | * [Use negation to handle undefined](#use-negation-to-handle-undefined) 43 | * [Consider partial helper rules over comprehensions in rule bodies](#consider-partial-helper-rules-over-comprehensions-in-rule-bodies) 44 | * [Avoid prefixing rules and functions with `get_` or `list_`](#avoid-prefixing-rules-and-functions-with-get_-or-list_) 45 | * [Prefer unconditional assignment in rule head over rule body](#prefer-unconditional-assignment-in-rule-head-over-rule-body) 46 | * [Variables and Data Types](#variables-and-data-types) 47 | * [Use `in` to check for membership](#use-in-to-check-for-membership) 48 | * [Prefer `some .. in` for iteration](#prefer-some--in-for-iteration) 49 | * [Use `every` to express FOR ALL](#use-every-to-express-for-all) 50 | * [Don't use unification operator for assignment or comparison](#dont-use-unification-operator-for-assignment-or-comparison) 51 | * [Don't use undeclared variables](#dont-use-undeclared-variables) 52 | * [Prefer sets over arrays (where applicable)](#prefer-sets-over-arrays-where-applicable) 53 | * [Functions](#functions) 54 | * [Prefer using arguments over `input`, `data` or rule references](#prefer-using-arguments-over-input-data-or-rule-references) 55 | * [Avoid using the last argument for the return value](#avoid-using-the-last-argument-for-the-return-value) 56 | * [Regex](#regex) 57 | * [Use raw strings for regex patterns](#use-raw-strings-for-regex-patterns) 58 | * [Packages](#packages) 59 | * [Package name should match file location](#package-name-should-match-file-location) 60 | * [Imports](#imports) 61 | * [Prefer importing packages over rules and functions](#prefer-importing-packages-over-rules-and-functions) 62 | * [Avoid importing `input`](#avoid-importing-input) 63 | * [Older Advice](#older-advice) 64 | * [Use explicit imports for future keywords](#use-explicit-imports-for-future-keywords) 65 | * [Contributing](#contributing) 66 | * [Community](#community) 67 | 68 | 69 | 70 | 71 | 72 | 73 | ## General Advice 74 | 75 | ### Optimize for readability, not performance 76 | 77 | Rego is a declarative language, which in the best case means you express **what** you want rather than **how** it 78 | should be retrieved. When authoring policy, do not try to be "smart" about assumed performance characteristics or 79 | optimizations. That's what OPA should worry about! 80 | 81 | Optimize for **readability** and **obviousness**. Optimize for performance _only_ if you've identified performance 82 | issues in your policy, and even if you do — making your policy more compact or "clever" almost never helps at addressing 83 | the problem at hand. 84 | 85 | #### Related Resources 86 | 87 | - [Policy Performance](https://www.openpolicyagent.org/docs/latest/policy-performance/) 88 | 89 | ### Use `opa fmt` 90 | 91 | The `opa fmt` tool ensures consistent formatting across teams and projects. While certainly not 92 | [perfect](https://github.com/open-policy-agent/opa/issues/4508) (yet!), unified formatting is a big win, and saves a 93 | lot of time in code reviews arguing over details around style. 94 | 95 | A good idea could be to run `opa fmt --write` on save, which can be configured in most editors. If you want to enforce 96 | `opa fmt` formatting as part of your build pipeline, use `opa fmt --fail`. 97 | 98 | In order to not flood this guide with data, formatting conventions covered by `opa fmt` will not be included here. 99 | 100 | **Tip**: `opa fmt` uses tabs for indentation. By default, GitHub uses 8 spaces to display tabs, which is arguably a bit 101 | much. You can change this preference for your account in 102 | [github.com/settings/appearance](https://github.com/settings/appearance), 103 | or provide an [.editorconfig](https://editorconfig.org/) file in your policy repository, which will be used by GitHub 104 | (and other tools) to properly display your Rego files: 105 | 106 | ```ini 107 | [*.rego] 108 | end_of_line = lf 109 | insert_final_newline = true 110 | charset = utf-8 111 | indent_style = tab 112 | indent_size = 4 113 | ``` 114 | 115 | Sadly, there doesn't seem to be a way to enforce this for code blocks displayed in markdown (`.md`) files. 116 | 117 | :::tip 118 | You can lint for this recommendation using the [`opa-fmt`](https://docs.styra.com/regal/rules/style/opa-fmt) 119 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 120 | ::: 121 | 122 | ### Use strict mode 123 | 124 | [Strict mode](https://www.openpolicyagent.org/docs/latest/strict/) provides extra checks for common mistakes like 125 | redundant imports, or unused variables. Include an `opa check --strict path/to/polices` step as part of your build 126 | pipeline. 127 | 128 | ### Use metadata annotations 129 | 130 | Favor [metadata annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata) over regular comments. 131 | 132 | Metadata annotations allow external tools and editors to parse their contents, potentially leveraging them for 133 | something useful, like in-line explanations, generated docs, etc. 134 | 135 | Annotations are also a good way to de-duplicate information such as documentation links, contact emails 136 | and error codes where explanations are returned as part of the result. 137 | 138 | **Avoid** 139 | ```rego 140 | # Example package with documentation 141 | package example 142 | 143 | import future.keywords.contains 144 | import future.keywords.if 145 | 146 | # E123: Deny non admin users. 147 | # Only admin users are allowed to access these resources, see https://docs.example.com/policy/rule/E123 148 | deny contains { 149 | "code": 401, 150 | "message": "Unauthorized due to policy rule (E123, https://docs.example.com/policy/rule/E123)", 151 | } if { 152 | input.admin == false 153 | } 154 | ``` 155 | 156 | **Prefer** 157 | ```rego 158 | # METADATA 159 | # title: Example 160 | # description: Example package with documentation 161 | package example 162 | 163 | import future.keywords.contains 164 | import future.keywords.if 165 | 166 | # METADATA 167 | # title: Deny non admin users 168 | # description: Only admin users are allowed to access these resources 169 | # related_resources: 170 | # - https://docs.example.com/policy/rule/E123 171 | # custom: 172 | # code: 401 173 | # error_id: E123 174 | deny contains { 175 | "code": metadata.custom.code, 176 | "message": sprintf("Unauthorized due to policy rule (%s, %s)", [ 177 | metadata.custom.error_id, 178 | concat(",", [ref | ref := metadata.related_resources[_].ref]), 179 | ]), 180 | } if { 181 | input.admin == false 182 | 183 | metadata := rego.metadata.rule() 184 | } 185 | ``` 186 | 187 | **Notes / Exceptions** 188 | 189 | Use regular comments inside of rule bodies, or for packages and rules you consider "internal". 190 | 191 | #### Related Resources 192 | 193 | - [Annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata) 194 | 195 | ### Get to know the built-in functions 196 | 197 | With more than 150 [built-in functions](https://www.openpolicyagent.org/docs/latest/policy-reference/#built-in-functions) 198 | tailor-made for policy evaluation, there's a good chance that some of them can help you accomplish your goal. 199 | 200 | ### Consider using JSON schemas for type checking 201 | 202 | As you author Rego policy, providing JSON schemas for your `input` (and possibly `data`) enables strict 203 | [type checking](https://www.openpolicyagent.org/docs/latest/schemas/), letting you avoid simple — but common — mistakes, 204 | like typos, or referencing nested attributes in the wrong location. This extra level of verification improves both the 205 | developer experience as well as the quality of your policies. 206 | 207 | ## Style 208 | 209 | ### Prefer snake_case for rule names and variables 210 | 211 | The built-in functions use `snake_case` for naming — follow that convention for your own rules, functions, and variables. 212 | 213 | **Avoid** 214 | ```rego 215 | userIsAdmin if "admin" in input.user.roles 216 | ``` 217 | 218 | **Prefer** 219 | ```rego 220 | user_is_admin if "admin" in input.user.roles 221 | ``` 222 | 223 | **Notes / Exceptions** 224 | 225 | In many cases, you might not control the format of the `input` data — if the domain of a policy (e.g. Envoy) 226 | mandates a different style, making an exception might seem reasonable. Adapting policy format after `input` is however 227 | prone to inconsistencies, as you'll likely end up mixing different styles in the same policy (due to imports of common 228 | code, etc). 229 | 230 | :::tip 231 | You can lint for this recommendation using the [`prefer-snake-case`](https://docs.styra.com/regal/rules/style/prefer-snake-case) 232 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 233 | ::: 234 | 235 | ### Optionally, use leading underscore for rules intended for internal use 236 | 237 | While OPA doesn't have "private" rules or functions, a pretty common convention that we've seen in the community is to 238 | use a leading underscore for rules and functions that are intended to be internal to the package that they are in: 239 | 240 | ```rego 241 | developers contains user if { 242 | some user in input.users 243 | _is_developer(user) 244 | } 245 | 246 | _is_developer(user) if { 247 | # some conditions 248 | } 249 | 250 | _is_developer(user) if { 251 | # some other conditions 252 | } 253 | ``` 254 | 255 | While an `is_developer` function may seem like a good candidate for reuse, it could easily be the case that this should 256 | be considered to what **this** package considers a developer, and not necessarily a universal truth. Using a leading 257 | underscore to denote this is a good way to communicate this intent, but there are also other ways to do this, like 258 | agreed upon naming conventions, or using custom metadata annotation attributes. 259 | 260 | One benefit of sticking to the leading underscore convention is that tools like [Regal](https://docs.styra.com/regal), 261 | and the language server for Rego that it provides, may use this information to provide better suggestions, like not 262 | adding references to these rules and functions from other packages. 263 | 264 | ### Keep line length `<=` 120 characters 265 | 266 | Long lines are tedious to read. Keep line length at 120 characters or below. 267 | 268 | **Avoid** 269 | ```rego 270 | frontend_admin_users := [username | some user in input.users; "frontend" in user.domains; "admin" in user.roles; username := user.username] 271 | ``` 272 | 273 | **Prefer** 274 | ```rego 275 | frontend_admin_users := [username | 276 | some user in input.users 277 | "frontend" in user.domains 278 | "admin" in user.roles 279 | username := user.username 280 | ] 281 | ``` 282 | 283 | :::tip 284 | You can lint for this recommendation using the [`line-length`](https://docs.styra.com/regal/rules/style/line-length) 285 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 286 | ::: 287 | 288 | ## Rules 289 | 290 | ### Use helper rules and functions 291 | 292 | Helper rules makes policies more readable, and for repeated conditions more performant as well. If your rule contains 293 | more than a few simple expressions, consider splitting it into multiple rules with good names. 294 | 295 | **Avoid** 296 | ```rego 297 | allow if { 298 | "developer" in input.user.roles 299 | input.request.method in {"GET", "HEAD"} 300 | startswith(input.request.path, "/docs") 301 | } 302 | 303 | allow if { 304 | "developer" in input.user.roles 305 | input.request.method in {"GET", "HEAD"} 306 | startswith(input.request.path, "/api") 307 | } 308 | ``` 309 | 310 | **Prefer** 311 | ```rego 312 | allow if { 313 | is_developer 314 | read_request 315 | startswith(input.request.path, "/docs") 316 | } 317 | 318 | allow if { 319 | is_developer 320 | read_request 321 | startswith(input.request.path, "/api") 322 | } 323 | 324 | read_request if input.request.method in {"GET", "HEAD"} 325 | 326 | is_developer if "developer" in input.user.roles 327 | ``` 328 | 329 | Additionally, helper rules and functions may be kept in (and imported from) separate modules, allowing you to build a 330 | logical — and reusable! — structure for your policy files. 331 | 332 | ### Use negation to handle undefined 333 | 334 | When encountering undefined references inside of rules, evaluation of the rule halts and the rule _itself_ evaluates to 335 | undefined, unless of course, a `default` value has been provided. While saying `allow is undefined` or `allow is false` 336 | if encountering undefined in a rule is likely desirable, this doesn't hold true when working with "inverted" rules - 337 | i.e. rules like `deny` (as opposed to `allow`). Saying `deny is undefined` or `deny is false` if undefined is 338 | encountered, essentially means that any occurrence of undefined (such as when attributes are missing in the input 339 | document) would lead to the `deny` rule not getting enforced. This is particularly common writing partial rules (i.e. 340 | rules that build [sets](https://www.openpolicyagent.org/docs/latest/policy-language/#generating-sets) or 341 | [objects](https://www.openpolicyagent.org/docs/latest/policy-language/#generating-objects)). 342 | 343 | Consider for example this simple rule: 344 | 345 | **Avoid** 346 | ```rego 347 | authorized := count(deny) == 0 348 | 349 | deny contains "User is anonymous" if input.user_id == "anonymous" 350 | ``` 351 | 352 | At first glance, it might seem obvious that evaluating the rule should add a violation to the set of messages if the 353 | `user_id` provided in `input` is equal to "anonymous". But what happens if there is no `user_id` provided _at all_? 354 | Evaluation will stop when encountering undefined, and the comparison will never be invoked, leading to **nothing** 355 | being added to the `deny` set — the rule allows someone without a `user_id`. We could of course add another 356 | rule, checking only for its presence: 357 | 358 | ```rego 359 | deny contains "User ID missing from input" if not input.user_id 360 | ``` 361 | 362 | This is nice in that we'll get an even more granular message returned to the caller, but quickly becomes tedious when 363 | working with a large set of input data. To deal with this, a helper rule using _negation_ may be used. 364 | 365 | **Prefer** 366 | ```rego 367 | authorized := count(deny) == 0 368 | 369 | deny contains "User is anonymous" if not authenticated_user 370 | 371 | authenticated_user if input.user_id != "anonymous" 372 | ``` 373 | 374 | In the above case, the `authenticated_user` rule will fail **both** in the the undefined case, and if defined 375 | but equal to "anonymous". Since we negate the result of the helper rule in the `deny` rule, we'll have both 376 | cases covered. 377 | 378 | #### Related Resources 379 | 380 | - [OPA AWS CloudFormation Hook Tutorial](https://www.openpolicyagent.org/docs/latest/aws-cloudformation-hooks/) 381 | 382 | ### Consider partial helper rules over comprehensions in rule bodies 383 | 384 | While comprehensions inside of rule bodies allows for compact rules, these are often harder to debug, and can't easily 385 | be reused by other rules. Partial rules may be referenced by any other rule, and more importantly, by you! 386 | Having many smaller, composable rules, is often key to quickly identifying where things fail, as each rule may be 387 | queried individually. 388 | 389 | **Avoid** 390 | ```rego 391 | allow if { 392 | input.request.method in {"GET", "HEAD"} 393 | input.request.path[0] == "credit_reports" 394 | input.user.name in {username | 395 | # These should not count as MFA 396 | insecure_methods := {"email"} 397 | 398 | some user in data.users 399 | mfa_methods := {method | some method in user.authentication.methods} - insecure_methods 400 | 401 | count(mfa_methods) > 1 402 | username := user.name 403 | } 404 | } 405 | ``` 406 | 407 | **Prefer** 408 | ```rego 409 | allow if { 410 | input.request.method in {"GET", "HEAD"} 411 | input.request.path[0] == "credit_reports" 412 | input.user.name in mfa_authenticated_users 413 | } 414 | 415 | mfa_authenticated_users contains username if { 416 | # These should not count as MFA 417 | insecure_methods := {"email"} 418 | 419 | some user in data.users 420 | mfa_methods := {method | some method in user.authentication.methods} - insecure_methods 421 | 422 | count(mfa_methods) > 1 423 | username := user.name 424 | } 425 | ``` 426 | 427 | **Notes / Exceptions** 428 | 429 | Does not apply if ordering is of importance, or duplicate values should be allowed. For those cases, use array 430 | comprehensions. 431 | 432 | ### Avoid prefixing rules and functions with `get_` or `list_` 433 | 434 | Since Rego evaluation is generally free of side effects, any rule or function is essentially a "getter". Adding a 435 | `get_` prefix to a rule or function (like `get_resources`) thus adds little of value compared to just naming it 436 | `resources`. Additionally, the type and return value of the rule should serve to tell whether a rule might return a 437 | single value (i.e. a complete rule) or a collection (a partial rule). 438 | 439 | **Avoid** 440 | ```rego 441 | get_first_name(user) := split(user.name, " ")[0] 442 | 443 | # Partial rule, so a set of users is to be expected 444 | list_developers contains user if { 445 | some user in data.application.users 446 | user.type == "developer" 447 | } 448 | ``` 449 | 450 | **Prefer** 451 | ```rego 452 | # "get" is implied 453 | first_name(user) := split(user.name, " ")[0] 454 | 455 | # Partial rule, so a set of users is to be expected 456 | developers contains user if { 457 | some user in data.application.users 458 | user.type == "developer" 459 | } 460 | ``` 461 | 462 | **Notes / Exceptions** 463 | 464 | Using `is_`, or `has_` for boolean helper functions, like `is_admin(user)` may be easier to comprehend than 465 | `admin(user)`. 466 | 467 | :::tip 468 | You can lint for this recommendation using the [`avoid-get-and-list-prefix`](https://docs.styra.com/regal/rules/style/avoid-get-and-list-prefix) 469 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 470 | ::: 471 | 472 | ### Prefer unconditional assignment in rule head over rule body 473 | 474 | Rules that return values unconditionally should place the assignment directly in the rule head, as doing so 475 | in the rule body adds unnecessary noise. 476 | 477 | **Avoid** 478 | ```rego 479 | full_name := name { 480 | name := concat(", ", [input.first_name, input.last_name]) 481 | } 482 | 483 | divide_by_ten(x) := y { 484 | y := x / 10 485 | } 486 | ``` 487 | 488 | **Prefer** 489 | ```rego 490 | full_name := concat(", ", [input.first_name, input.last_name]) 491 | 492 | divide_by_ten(x) := x / 10 493 | ``` 494 | 495 | :::tip 496 | You can lint for this recommendation using the [`unconditional-assignment`](https://docs.styra.com/regal/rules/style/unconditional-assignment) 497 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 498 | ::: 499 | 500 | ## Variables and Data Types 501 | 502 | ### Use `in` to check for membership 503 | 504 | Using `in` for membership checks clearly communicates intent, and is less prone to errors. This is especially true when 505 | checking if something is _not_ part of a collection. 506 | 507 | **Avoid** 508 | ```rego 509 | # "Old" way of checking for membership - iteration + comparison 510 | allow { 511 | "admin" == input.user.roles[_] 512 | } 513 | ``` 514 | 515 | **Prefer** 516 | ```rego 517 | allow if "admin" in input.user.roles 518 | ``` 519 | 520 | **Avoid** 521 | ```rego 522 | deny contains "Only admin allowed" if not user_is_admin 523 | 524 | user_is_admin if { 525 | "admin" == input.user.roles[_] 526 | } 527 | ``` 528 | 529 | **Prefer** 530 | ```rego 531 | deny contains "Only admin allowed" if not "admin" in input.user.roles 532 | ``` 533 | 534 | :::tip 535 | You can lint for this recommendation using the [`use-in-operator`](https://docs.styra.com/regal/rules/idiomatic/use-in-operator) 536 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 537 | ::: 538 | 539 | ### Prefer `some .. in` for iteration 540 | 541 | Using the `some` .. `in` construct for iteration removes ambiguity around iteration vs. membership checks, and is 542 | generally more pleasant to read. 543 | 544 | **Avoid** 545 | ```rego 546 | my_rule if { 547 | # Are we iterating users over a partial "other_rule" here, 548 | # or checking if the set contains a user defined elsewhere? 549 | other_rule[user] 550 | } 551 | ``` 552 | 553 | While this could be alleviated by declaring `some user` before the iteration, we can't take that consideration for 554 | granted when reading code from someone else. 555 | 556 | **Avoid** 557 | ```rego 558 | # Iterating over array 559 | internal_hosts contains hostname if { 560 | host := data.network.hosts[_] 561 | host.internal == true 562 | hostname := host.name 563 | } 564 | 565 | # Iterating over object 566 | public_endpoints contains endpoint if { 567 | some endpoint 568 | attributes := endpoints[endpoint] 569 | attributes.public 570 | } 571 | ``` 572 | 573 | **Prefer** 574 | ```rego 575 | internal_hosts contains hostname if { 576 | some host in data.network.hosts 577 | host.internal == true 578 | hostname := host.name 579 | } 580 | 581 | # Iterating over object 582 | public_endpoints contains endpoint if { 583 | some endpoint, attributes in endpoints 584 | attributes.public 585 | } 586 | ``` 587 | 588 | **Notes / Exceptions** 589 | 590 | Using the "old" style of iteration may still be preferable when iterating over deeply nested structures. 591 | 592 | ```rego 593 | # Building a list of all hostnames from a deeply nested structure 594 | 595 | all_hostnames := [hostname | hostname := data.regions[_].networks[_].servers[_].hostname] 596 | 597 | # ⬆️ is likely preferable over ⬇️ 598 | 599 | all_hostnames := [hostname | 600 | some region in data.regions 601 | some network in region 602 | some server in network 603 | hostname := server.hostname 604 | ] 605 | ``` 606 | 607 | :::tip 608 | You can lint for this recommendation using the [`prefer-some-in-iteration`](https://docs.styra.com/regal/rules/style/prefer-some-in-iteration) 609 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 610 | ::: 611 | 612 | ### Use `every` to express FOR ALL 613 | 614 | The `every` keyword makes it trivial to describe "for all" type expressions, which previously required the use of 615 | helper rules, or comparing counts of items in the original collection against a filtered one produced by a 616 | comprehension. 617 | 618 | **Avoid** 619 | ```rego 620 | # Negate result of _any_ match 621 | allow if not any_old_registry 622 | 623 | any_old_registry if { 624 | some container in input.request.object.spec.containers 625 | startswith(container.image, "old.docker.registry/") 626 | } 627 | ``` 628 | 629 | **Prefer** 630 | ```rego 631 | allow if { 632 | every container in input.request.object.spec.containers { 633 | not startswith(container.image, "old.docker.registry/") 634 | } 635 | } 636 | ``` 637 | 638 | **Avoid** 639 | ```rego 640 | words := ["always", "arbitrary", "air", "brand", "asphalt"] 641 | 642 | all_starts_with_a if { 643 | starts_with_a := [word | 644 | some word in words 645 | startswith(word, "a") 646 | ] 647 | count(starts_with_a) == count(words) 648 | } 649 | ``` 650 | 651 | **Prefer** 652 | ```rego 653 | words := ["always", "arbitrary", "air", "brand", "asphalt"] 654 | 655 | all_starts_with_a if { 656 | every word in words { 657 | startswith(word, "a") 658 | } 659 | } 660 | ``` 661 | 662 | **Notes / Exceptions** 663 | 664 | Older versions of OPA used the `all` built-in function to check that all elements of an array had the value `true`. 665 | This function has been deprecated for a long time, and will eventually be removed. 666 | 667 | ### Don't use unification operator for assignment or comparison 668 | 669 | The [unification](https://www.openpolicyagent.org/docs/latest/policy-language/#unification-) operator (`=`) allows you 670 | to combine assignment and comparison. While this is useful in a few specific cases (see "Notes / Exceptions" below), 671 | using the assignment operator (`:=`) for assignment, and the comparison operator (`==`) for comparison, is almost always 672 | preferable. Separating assignment from comparison clearly demonstrates intent, and removes the ambiguity around scope 673 | associated with unification. 674 | 675 | **Avoid** 676 | ```rego 677 | # Top level assignment using unification operator 678 | roles = input.user.roles 679 | 680 | allow if { 681 | # Unification operator - used for assignment to `username` variable or for 682 | # comparing to a `username` variable or rule defined elsewhere? Who knows. 683 | username = input.user.name 684 | 685 | # ... 686 | } 687 | 688 | allow if { 689 | # Unification operator used for comparison 690 | input.request.method = "GET" 691 | } 692 | 693 | allow if { 694 | some user 695 | input.request.path = ["users", user] 696 | input.request.user == user 697 | } 698 | ``` 699 | 700 | **Prefer** 701 | ```rego 702 | # Top level assignment using assignment operator 703 | roles := input.user.roles 704 | 705 | allow if { 706 | # Assignment operator used for assignment - no ambiguity around 707 | # intent, or variable scope 708 | username := input.user.name 709 | 710 | # ... do something with username 711 | } 712 | 713 | allow if { 714 | # Comparison operator used for comparison 715 | input.request.method == "GET" 716 | } 717 | 718 | allow if { 719 | input.request.path == ["users", input.request.user] 720 | } 721 | ``` 722 | 723 | **Notes / Exceptions** 724 | 725 | Unification was used extensively in older versions of OPA, and following that, in the policy examples provided in 726 | the OPA documentation, blogs, and elsewhere. With the assignment and comparison operators now available for use in 727 | any context, there are generally few reasons to use the unification operator in modern Rego. 728 | 729 | One notable exception is when matching for example, the path of a request (as presented in array form), 730 | where you'll want to do both comparison and assignment to variables from the path components: 731 | 732 | ```rego 733 | # Using unification - compact but clear 734 | router { 735 | some user_id, podcast_id 736 | ["users", user_id, "podcasts", podcast_id] = input.request.path 737 | 738 | # .. do something with user_id, podcast_id 739 | } 740 | 741 | # Using comparison + assignment - arguably messier 742 | router { 743 | input.request_path[0] == "users" 744 | input.request_path[2] == "podcasts" 745 | 746 | user_id := input.request_path[1] 747 | podcast_id := input.request_path[3] 748 | 749 | # .. do something with user_id, podcast_id 750 | } 751 | ``` 752 | 753 | #### Related Resources 754 | 755 | - [Strict-mode to phase-out the "single =" operator](https://github.com/open-policy-agent/opa/issues/4688) 756 | - [OPA fmt 2.0](https://github.com/open-policy-agent/opa/issues/4508) 757 | 758 | :::tip 759 | You can lint for this recommendation using the [`use-assignment-operator`](https://docs.styra.com/regal/rules/style/use-assignment-operator) 760 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 761 | ::: 762 | 763 | ### Don't use undeclared variables 764 | 765 | Using undeclared variables (i.e. not declared using `some` or `:=`) makes it harder to understand what's going on 766 | in a rule, and introduces ambiguities around scope. 767 | 768 | **Avoid** 769 | ```rego 770 | messages contains message if { 771 | message := input.topics[topic].body 772 | } 773 | ``` 774 | 775 | **Prefer** 776 | ```rego 777 | messages contains message if { 778 | some topic 779 | message := input.topics[topic].body 780 | } 781 | 782 | # Alternatively 783 | messages contains message if { 784 | some topic in input.topics 785 | message := topic.body 786 | } 787 | 788 | # or 789 | 790 | messages contains message if { 791 | message := input.topics[_].body 792 | } 793 | ``` 794 | 795 | :::tip 796 | You can lint for this recommendation using the [`use-some-for-output-vars`](https://docs.styra.com/regal/rules/idiomatic/use-some-for-output-vars) 797 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 798 | ::: 799 | 800 | ### Prefer sets over arrays (where applicable) 801 | 802 | For any _unordered_ sequence of _unique_ values, prefer to use 803 | [sets](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets) over 804 | [arrays](https://www.openpolicyagent.org/docs/latest/policy-reference/#arrays). 805 | 806 | This is almost always the case for common policy data like **roles** and **permissions**. 807 | For any applicable sequence of values, sets have the following benefits over arrays: 808 | 809 | - Clearly communicate uniqueness and non-ordered characteristics 810 | - Performance: set lookups are O(1) while array lookups are O(n) 811 | - Powerful [set operations](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets-2) available 812 | 813 | **Avoid** 814 | ```rego 815 | required_roles := ["accountant", "reports-writer"] 816 | provided_roles := [role | some role in input.user.roles] 817 | 818 | allow if { 819 | every required_role in required_roles { 820 | required_role in provided_roles 821 | } 822 | } 823 | ``` 824 | 825 | **Prefer** 826 | ```rego 827 | required_roles := {"accountant", "reports-writer"} 828 | provided_roles := {role | some role in input.user.roles} 829 | 830 | allow if { 831 | every required_role in required_roles { 832 | required_role in provided_roles 833 | } 834 | } 835 | ``` 836 | 837 | **Prefer** 838 | ```rego 839 | # Alternatively, use set intersection 840 | allow if { 841 | required_roles & provided_roles == required_roles 842 | } 843 | ``` 844 | 845 | #### Related Resources 846 | 847 | - [Five things you didn't know about OPA](https://blog.styra.com/blog/five-things-you-didnt-know-about-opa). 848 | 849 | ## Functions 850 | 851 | ### Prefer using arguments over `input`, `data` or rule references 852 | 853 | What separates functions from rules is that they accept _arguments_. While a function too may reference anything from 854 | `input`, `data` or other rules declared in a policy, these references create dependencies that aren't obvious simply by 855 | checking the function signature, and it makes it harder to reuse that function in other contexts. Additionally, 856 | functions that only depend on their arguments are easier to test standalone. 857 | 858 | **Avoid** 859 | ```rego 860 | # Depends on both `input` and `data` 861 | is_preferred_login_method(method) if { 862 | preferred_login_methods := {login_method | 863 | some login_method in data.authentication.all_login_methods 864 | login_method in input.user.login_methods 865 | } 866 | method in preferred_login_methods 867 | } 868 | ``` 869 | 870 | **Prefer** 871 | ```rego 872 | # Depends only on function arguments 873 | is_preferred_login_method(method, user, all_login_methods) if { 874 | preferred_login_methods := {login_method | 875 | some login_method in all_login_methods 876 | login_method in user.login_methods 877 | } 878 | method in preferred_login_methods 879 | } 880 | ``` 881 | 882 | ### Avoid using the last argument for the return value 883 | 884 | Older Rego policies sometimes contain an unusual way to declare where the return value of a function call should be 885 | stored — the last argument of the function. True to it's 886 | [Datalog](https://www.openpolicyagent.org/docs/latest/policy-language/#what-is-rego) roots, return values may be stored 887 | either using assignment (i.e. `:=`) or by appending a variable name to the argument list of a function. These two 888 | expressions are thus equivalent: 889 | 890 | **Avoid** 891 | ```rego 892 | first_a := i if { 893 | indexof("answer", "a", i) 894 | } 895 | ``` 896 | 897 | **Prefer** 898 | ```rego 899 | first_a := i if { 900 | i := indexof("answer", "a") 901 | } 902 | ``` 903 | 904 | While the first form is valid, it is almost guaranteed to confuse developers coming from the most common programming 905 | languages. Again, optimize for readability! 906 | 907 | :::tip 908 | You can lint for this recommendation using the [`function-arg-return`](https://docs.styra.com/regal/rules/style/function-arg-return) 909 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 910 | ::: 911 | 912 | ## Regex 913 | 914 | ### Use raw strings for regex patterns 915 | 916 | [Raw strings](https://www.openpolicyagent.org/docs/edge/policy-language/#strings) are interpreted literally, allowing 917 | you to avoid having to escape special characters like `\` in your regex patterns. 918 | 919 | **Avoid** 920 | ```rego 921 | all_digits if { 922 | regex.match("[\\d]+", "12345") 923 | } 924 | ``` 925 | 926 | **Prefer** 927 | ```rego 928 | all_digits if { 929 | regex.match(`[\d]+`, "12345") 930 | } 931 | ``` 932 | 933 | :::tip 934 | You can lint for this recommendation using the [`non-raw-regex-pattern`](https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern) 935 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 936 | ::: 937 | 938 | ## Packages 939 | 940 | ### Package name should match file location 941 | 942 | When naming packages, the package name should reflect the file location. This 943 | makes the package implementation easier to find when looking up from elsewhere 944 | in a project as well. 945 | 946 | When choosing to follow this recommendation, there are two options: 947 | 948 | - **Matching the directory and filename** 949 | - Pros: Reduced nesting for simple policies. 950 | - Cons: Large packages can become unwieldy in long files. 951 | - **Matching the directory only** 952 | - Pros: Large packages can be broken into many files. 953 | - Cons: Exception needed to co-locate test files (i.e. `package foo_test` 954 | should still be in `foo/`). 955 | 956 | Either is acceptable, just remember to use the same convention throughout 957 | your project. 958 | 959 | #### Matching the directory and filename 960 | 961 | **Avoid** 962 | ```rego 963 | # foo/bar.rego 964 | package bar.foo 965 | 966 | # ... 967 | ``` 968 | 969 | **Prefer** 970 | ```rego 971 | # foo/bar.rego 972 | package foo.bar 973 | 974 | # ... 975 | ``` 976 | 977 | #### Matching the directory only 978 | 979 | **Avoid** 980 | ```rego 981 | # foo/bar.rego 982 | package baz 983 | 984 | # ... 985 | ``` 986 | 987 | **Prefer** 988 | ```rego 989 | # foo/bar.rego 990 | package foo 991 | 992 | # ... 993 | ``` 994 | 995 | ## Imports 996 | 997 | ### Prefer importing packages over rules and functions 998 | 999 | Importing packages rather than specific rules and functions allows you to reference them by the package name, making it 1000 | obvious where the rule or function was declared. Additionally, well-named packages help provide context to assertions. 1001 | 1002 | **Avoid** 1003 | ```rego 1004 | import data.user.is_admin 1005 | 1006 | allow if is_admin 1007 | ``` 1008 | 1009 | **Prefer** 1010 | ```rego 1011 | import data.user 1012 | 1013 | allow if user.is_admin 1014 | ``` 1015 | 1016 | :::tip 1017 | You can lint for this recommendation using the [`prefer-package-imports`](https://docs.styra.com/regal/rules/imports/prefer-package-imports) 1018 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 1019 | ::: 1020 | 1021 | ### Avoid importing `input` 1022 | 1023 | While importing attributes from the global `input` variable might eliminate some levels of nesting, it makes the origin 1024 | of the attribute(s) less apparent. Clearly differentiating `input` and `data` from values, functions, and rules 1025 | defined inside of the same package helps in making things _obvious_, and few things beat obviousness! 1026 | 1027 | **Avoid** 1028 | ```rego 1029 | import input.request.context.user 1030 | 1031 | # ... many lines of code later 1032 | 1033 | fin_dept if { 1034 | # where does "user" come from? 1035 | contains(user.department, "finance") 1036 | } 1037 | ``` 1038 | 1039 | **Prefer** 1040 | ```rego 1041 | fin_dept if { 1042 | contains(input.request.context.user.department, "finance") 1043 | } 1044 | ``` 1045 | 1046 | **Prefer** 1047 | ```rego 1048 | fin_dept if { 1049 | # Alternatively, assign an intermediate variable close to where it's referenced 1050 | user := input.request.context.user 1051 | contains(user.department, "finance") 1052 | } 1053 | ``` 1054 | 1055 | **Notes / Exceptions** 1056 | 1057 | In some contexts, the source of data is obvious even when imported and/or renamed. A common practice is 1058 | to rename `input` in Terraform policies for example, either via `import` or a new top-level variable. 1059 | 1060 | ```rego 1061 | import input as tfplan 1062 | 1063 | violations contains message if { 1064 | # still obvious where "tfplan" comes from, perhaps even more so — this is generally acceptable 1065 | some change in tfplan.resource_changes 1066 | # ... 1067 | } 1068 | ``` 1069 | 1070 | :::tip 1071 | You can lint for this recommendation using the [`avoid-importing-input`](https://docs.styra.com/regal/rules/imports/avoid-importing-input) 1072 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 1073 | ::: 1074 | 1075 | ## Older Advice 1076 | 1077 | Advice placed here was valid at the time, but has since been replaced by new recommendations. Kept here for the sake 1078 | of completeness, and to provide context for older policies. 1079 | 1080 | ### Use explicit imports for future keywords 1081 | 1082 | **With the introduction of the `import rego.v1` construct in OPA v0.59.0, this is no longer needed** 1083 | 1084 | In order to evolve the Rego language without breaking existing policies, many new features require importing 1085 | ["future" keywords](https://www.openpolicyagent.org/docs/latest/policy-language/#future-keywords), like `contains`, 1086 | `every`, `if` and `in`. While it might seem convenient to use the "catch-all" form of `import future.keywords` to 1087 | import all of the future keywords, this construct risks breaking your policies when new keywords are introduced, and 1088 | their names happen to collide with names you've used for variables or rules. 1089 | 1090 | **Avoid** 1091 | ```rego 1092 | import future.keywords 1093 | 1094 | severe_violations contains violation if { 1095 | some violation in input.violations 1096 | violation.severity > 5 1097 | } 1098 | ``` 1099 | 1100 | **Prefer** 1101 | ```rego 1102 | import future.keywords.contains 1103 | import future.keywords.if 1104 | import future.keywords.in 1105 | 1106 | severe_violations contains violation if { 1107 | some violation in input.violations 1108 | violation.severity > 5 1109 | } 1110 | ``` 1111 | 1112 | **Tip**: Importing the `every` keyword implicitly imports `in` as well, as it is required by the `every` construct. 1113 | Leaving out the import of `in` when `every` is imported is considered okay. 1114 | 1115 | :::tip 1116 | You can lint for this recommendation using the [`implicit-future-keywords`](https://docs.styra.com/regal/rules/imports/implicit-future-keywords) 1117 | Regal rule. Get started with [Regal, the Rego linter](https://docs.styra.com/regal). 1118 | ::: 1119 | 1120 | --- 1121 | 1122 | ## Contributing 1123 | 1124 | This document is meant to reflect the style preferences and best practices as compiled by the OPA community. As such, 1125 | we welcome contributions from any of its members. Since most of the topics in a guide like this are likely subject to 1126 | discussion, please open an issue, and allow some time for people to comment, before opening a PR. 1127 | 1128 | If you'd like to add or remove items for your own company, team or project, forking this repo is highly encouraged! 1129 | 1130 | ## Community 1131 | 1132 | For questions, discussions and announcements related to 1133 | Styra products, services and open source projects, please join 1134 | the Styra community on [Slack](https://inviter.co/styra)! 1135 | --------------------------------------------------------------------------------