├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── main.yml │ └── release.yml ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── dev ├── dev.cmd ├── run └── run.cmd ├── examples ├── fromAutomaton │ ├── openweather.json │ ├── wttr-in.json │ └── xkcd.json ├── getsandbox.yaml ├── github.yaml ├── opencage.yaml ├── openweathermap.yaml ├── petstore.yaml └── vonage.yaml ├── jest.config.js ├── package.json ├── src ├── commands │ └── lint.ts ├── index.ts └── spectral │ ├── functions │ ├── missing-input-schema.ts │ ├── oas2-content-negotiation.ts │ ├── oas2-unsupported-media-type.ts │ ├── oas3-unsupported-media-type.ts │ ├── operation-error-response.ts │ └── sf-oas3-missing-schema-property-example.ts │ ├── index.ts │ ├── lint.ts │ ├── ruleset.ts │ ├── tests │ ├── helpers │ │ └── test-rule.ts │ ├── sf-missing-input-schema.test.ts │ ├── sf-missing-operation-summary.test.ts │ ├── sf-oas2-allOf.test.ts │ ├── sf-oas2-content-negotiation.test.ts │ ├── sf-oas2-missing-schema-property-example.test.ts │ ├── sf-oas2-unsupported-media-type.test.ts │ ├── sf-oas3-allOf.test.ts │ ├── sf-oas3-anyOf.test.ts │ ├── sf-oas3-missing-schema-property-example.test.ts │ ├── sf-oas3-oneOf.test.ts │ ├── sf-oas3-unsupported-media-type.test.ts │ └── sf-operation-error-response.test.ts │ └── utils │ ├── format.ts │ └── index.ts ├── tsconfig.json ├── tsconfig.release.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.json'], 7 | }, 8 | plugins: [ 9 | '@typescript-eslint', 10 | 'simple-import-sort', 11 | ], 12 | extends: [ 13 | 'eslint:recommended', 14 | 'plugin:@typescript-eslint/eslint-recommended', 15 | 'plugin:@typescript-eslint/recommended', 16 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 17 | 'plugin:import/errors', 18 | 'plugin:import/warnings', 19 | 'prettier', 20 | ], 21 | rules: { 22 | 'newline-before-return': 'error', 23 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 24 | 'simple-import-sort/imports': 'error', 25 | 'sort-imports': 'off', 26 | 'import/first': 'error', 27 | 'import/newline-after-import': 'error', 28 | 'import/no-duplicates': 'error', 29 | 'no-multiple-empty-lines': 'error', 30 | 'lines-between-class-members': 'off', 31 | '@typescript-eslint/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true, exceptAfterOverload: true }], 32 | '@typescript-eslint/require-await': 'off', 33 | 'quotes': 'off', 34 | '@typescript-eslint/quotes': ['warn', 'single', { avoidEscape: true }], 35 | '@typescript-eslint/no-inferrable-types': ['warn'] 36 | }, 37 | settings: { 38 | 'import/parsers': { 39 | '@typescript-eslint/parser': ['.ts'], 40 | }, 41 | 'import/resolver': { 42 | typescript: { 43 | alwaysTryTypes: true, 44 | }, 45 | }, 46 | }, 47 | overrides: [{ 48 | files: '*.test.ts', 49 | rules: { 50 | '@typescript-eslint/no-explicit-any': 'off', 51 | '@typescript-eslint/no-unsafe-assignment': 'off', 52 | '@typescript-eslint/no-non-null-assertion': 'off', 53 | '@typescript-eslint/unbound-method': 'off', 54 | } 55 | }], 56 | }; 57 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | # Setup environment and checkout the project master 10 | - name: Setup Node.js environment 11 | uses: actions/setup-node@v2 12 | with: 13 | registry-url: https://registry.npmjs.org/ 14 | scope: '@superfaceai' 15 | node-version: '14' 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v2.3.4 19 | 20 | # Setup yarn cache 21 | - name: Get yarn cache directory path 22 | id: yarn-cache-dir-path 23 | run: echo "::set-output name=dir::$(yarn cache dir)" 24 | - uses: actions/cache@v2.1.3 25 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 26 | with: 27 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 28 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-yarn- 31 | 32 | # Install and run tests 33 | - name: Install dependencies 34 | run: yarn install 35 | - name: Build 36 | run: yarn build 37 | - name: Test 38 | run: yarn test --bail 39 | 40 | lint: 41 | runs-on: ubuntu-latest 42 | steps: 43 | # Setup environment and checkout the project master 44 | - name: Setup Node.js environment 45 | uses: actions/setup-node@v2 46 | with: 47 | registry-url: https://registry.npmjs.org/ 48 | scope: '@superfaceai' 49 | node-version: '14' 50 | 51 | - name: Checkout 52 | uses: actions/checkout@v2.3.4 53 | 54 | # Setup yarn cache 55 | - name: Get yarn cache directory path 56 | id: yarn-cache-dir-path 57 | run: echo "::set-output name=dir::$(yarn cache dir)" 58 | - uses: actions/cache@v2.1.3 59 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 60 | with: 61 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 62 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 63 | restore-keys: | 64 | ${{ runner.os }}-yarn- 65 | 66 | # Install and run lint 67 | - name: Install dependencies 68 | run: yarn install 69 | - name: Lint 70 | run: yarn lint 71 | 72 | license-check: 73 | runs-on: ubuntu-latest 74 | steps: 75 | # Setup environment and checkout the project master 76 | - name: Setup Node.js environment 77 | uses: actions/setup-node@v2 78 | with: 79 | registry-url: https://registry.npmjs.org/ 80 | scope: '@superfaceai' 81 | node-version: '14' 82 | 83 | - name: Checkout 84 | uses: actions/checkout@v2.3.4 85 | 86 | # Setup yarn cache 87 | - name: Get yarn cache directory path 88 | id: yarn-cache-dir-path 89 | run: echo "::set-output name=dir::$(yarn cache dir)" 90 | - uses: actions/cache@v2.1.3 91 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 92 | with: 93 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 94 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 95 | restore-keys: | 96 | ${{ runner.os }}-yarn- 97 | 98 | # Install and run license checker 99 | - name: Install dependencies 100 | run: yarn install 101 | - name: Install License checker 102 | run: | 103 | yarn global add license-checker 104 | echo "$(yarn global bin)" >> $GITHUB_PATH 105 | - name: Check licenses 106 | run: "license-checker --onlyAllow '0BDS;MIT;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;CC-BY-4.0;CC-BY-3.0;BSD;CC0-1.0;Unlicense;WTFPL' --summary" 107 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release package 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | release-level: 6 | description: 'Release level (one of): patch, minor, major, prepatch, preminor, premajor, prerelease' 7 | required: true 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Checkout project main and setup environment 13 | - name: Checkout 14 | uses: actions/checkout@v2.3.4 15 | 16 | - name: Setup Node.js environment 17 | uses: actions/setup-node@v2 18 | with: 19 | registry-url: https://registry.npmjs.org/ 20 | node-version: '16' 21 | 22 | # Install dependencies and run test 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | 26 | # Build the project 27 | - name: Build 28 | run: yarn build 29 | 30 | # Test the project 31 | - name: Tests 32 | run: yarn test 33 | 34 | # Git configuration 35 | - name: Git configuration 36 | run: | 37 | git config --global user.email "bot@superface.ai" 38 | git config --global user.name "GitHub Actions release workflow" 39 | 40 | - name: Bump release version 41 | if: startsWith(github.event.inputs.release-level, 'pre') != true 42 | run: | 43 | echo "NEW_VERSION=$(npm --no-git-tag-version version $RELEASE_LEVEL)" >> $GITHUB_ENV 44 | echo "RELEASE_TAG=latest" >> $GITHUB_ENV 45 | env: 46 | RELEASE_LEVEL: ${{ github.event.inputs.release-level }} 47 | 48 | - name: Bump pre-release version 49 | if: startsWith(github.event.inputs.release-level, 'pre') && github.ref_name != 'dev' 50 | run: | 51 | echo "NEW_VERSION=$(npm --no-git-tag-version --preid=beta version $RELEASE_LEVEL)" >> $GITHUB_ENV 52 | echo "RELEASE_TAG=beta" >> $GITHUB_ENV 53 | env: 54 | RELEASE_LEVEL: ${{ github.event.inputs.release-level }} 55 | 56 | - name: Bump rc pre-release version 57 | if: startsWith(github.event.inputs.release-level, 'pre') && github.ref_name == 'dev' 58 | run: | 59 | echo "NEW_VERSION=$(npm --no-git-tag-version --preid=rc version $RELEASE_LEVEL)" >> $GITHUB_ENV 60 | echo "RELEASE_TAG=next" >> $GITHUB_ENV 61 | env: 62 | RELEASE_LEVEL: ${{ github.event.inputs.release-level }} 63 | 64 | # Update changelog unreleased section with new version 65 | - name: Update changelog 66 | if: startsWith(github.event.inputs.release-level, 'pre') != true 67 | uses: superfaceai/release-changelog-action@v1 68 | with: 69 | path-to-changelog: CHANGELOG.md 70 | version: ${{ env.NEW_VERSION }} 71 | operation: release 72 | 73 | # Commit changelog changes 74 | - name: Commit CHANGELOG.md and package.json changes and create tag 75 | run: | 76 | git add "package.json" 77 | git add "CHANGELOG.md" 78 | git commit -m "chore: release ${{ env.NEW_VERSION }}" 79 | git tag ${{ env.NEW_VERSION }} 80 | 81 | # Publish version to public repository 82 | - name: Publish 83 | run: yarn publish --verbose --access public --tag ${{ env.RELEASE_TAG }} 84 | env: 85 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_BOT_PAT }} 86 | 87 | # Push changes to origin 88 | - name: Push changes to repository 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | run: | 92 | git push origin && git push --tags 93 | 94 | # Read version changelog 95 | - id: get-changelog 96 | name: Get release version changelog 97 | if: startsWith(github.event.inputs.release-level, 'pre') != true 98 | uses: superfaceai/release-changelog-action@v1 99 | with: 100 | path-to-changelog: CHANGELOG.md 101 | version: ${{ env.NEW_VERSION }} 102 | operation: read 103 | 104 | # Update release documentation 105 | - name: Update GitHub release documentation 106 | uses: softprops/action-gh-release@v1 107 | with: 108 | tag_name: ${{ env.NEW_VERSION }} 109 | body: ${{ steps.get-changelog.outputs.changelog }} 110 | prerelease: ${{ startsWith(github.event.inputs.release-level, 'pre') }} 111 | env: 112 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # yalc data 107 | .yalc/ 108 | yalc.lock 109 | 110 | testground 111 | /test/ 112 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | quoteProps: "as-needed", 8 | trailingComma: "es5", 9 | bracketSpacing: true, 10 | arrowParens: "avoid", 11 | endOfLine: "lf", 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## 0.1.0 - 2022-09-22 11 | ### Added 12 | - custom ruleset for validating OAS with purpose of automataic codegen 13 | - `openapi-linter` CLI 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Superface s.r.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Linter 2 | 3 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/superfaceai/openapi-linter/CI)](https://github.com/superfaceai/openapi-linter/actions/workflows/main.yml) 4 | [![npm](https://img.shields.io/npm/v/@superfaceai/openapi-linter)](https://www.npmjs.com/package/@superfaceai/openapi-linter) 5 | [![license](https://img.shields.io/npm/l/@superfaceai/openapi-linter)](LICENSE) 6 | [![CLI built with oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io) 7 | [![GitHub Discussions](https://img.shields.io/github/discussions/superfaceai/.github?logo=github&logoColor=fff)](https://github.com/orgs/superfaceai/discussions) 8 | 9 | > Is your OpenAPI Spec ready for SDK generators? 10 | 11 | OpenAPI Linter is a CLI and a Node.js library to validate OpenAPI specification. 12 | It is based on [Spectral] by Stoplight with [OpenAPI rules](https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules) with additional rules. The goal of Linter is to check whether the spec contains enough information to generate high quality, well documented SDK. 13 | We use OpenAPI Linter in [Superface Integration Designer][designer], but any client code generator benefits from well written OpenAPI specs. 14 | 15 | ## Setup 16 | 17 | Install from npm globally: 18 | 19 | ```shell 20 | npm i -g @superfaceai/openapi-linter 21 | ``` 22 | 23 | Now you can use the linter with commands `openapi-linter` or `oal`. 24 | 25 | Alternatively you can use the linter without installation with `npx`: 26 | 27 | ``` 28 | npx @superfaceai/openapi-linter lint 29 | ``` 30 | 31 | ## CLI commands 32 | 33 | 34 | 35 | - [`openapi-linter lint SPECIFICATIONPATH`](#openapi-linter-lint-specificationpath) 36 | 37 | ### `openapi-linter lint SPECIFICATIONPATH` 38 | 39 | Lints OpenAPI specification using three different parsers/validators. 40 | 41 | ``` 42 | USAGE 43 | $ openapi-linter lint [SPECIFICATIONPATH] [-f yaml|json] [-e error|warning|any] 44 | 45 | ARGUMENTS 46 | SPECIFICATIONPATH Path or URL to specification file 47 | 48 | FLAGS 49 | -e, --throwOn=