Skip to content

Commit

Permalink
feat: add built-in recommeded-strict ruleset (#1311)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatomyr authored Oct 26, 2023
1 parent 73d96a2 commit 4620f79
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changeset/beige-boats-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@redocly/openapi-core': minor
'@redocly/cli': minor
---

Added `recommended-strict` ruleset which uses the same rules as `recommended` but with the severity level set to `error` for all rules.
2 changes: 1 addition & 1 deletion docs/commands/lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ redocly lint --config=./another/directory/config.yaml

### Extend configuration

The `--extends` option allows you to extend the existing configuration. This option accepts one of the following values: `minimal`, `recommended`, or `all`. Each of the values is a base set of rules that the lint command uses. You can further modify this set in cases when you want to have your own set of rules based on the existing one, including particular rules that cover your specific needs.
The `--extends` option allows you to extend the existing configuration. This option accepts one of the following values: `minimal`, `recommended`, `recommended-strict` or `all`. Each of the values is a base set of rules that the lint command uses. You can further modify this set in cases when you want to have your own set of rules based on the existing one, including particular rules that cover your specific needs.

{% admonition type="warning" name="Important" %}
When you run the `lint` command without a configuration file, it uses the `extends: [recommended]` by default.
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/extends.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ To override the default settings, you can either configure different settings fo
The `extends` list is structured as an array of strings.
It supports the following types of values:

- Built-in ruleset name (`minimal` or `recommended`)
- Built-in ruleset name (`minimal`, `recommended` or `recommended-strict`)
- A plugin's registered configuration name
- Path or URL to another Redocly configuration file containing rules, preprocessors, decorators or custom plugins.

Expand Down
1 change: 1 addition & 0 deletions docs/guides/configure-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ In this guide, learn how to choose and adapt the rules built into Redocly for yo
To get started, try one of the existing rulesets and see if it meets your needs.

* The [`recommended`](../rules/recommended.md) ruleset has a good basic set of rules for a consistent, user-friendly API.
* The [recommended-strict](../rules/recommended.md#recommended-strict-ruleset) ruleset is identical to the `recommended`, except it elevates all warnings to errors so that you don't miss the warnings, i.e. in a CI pipeline.
* Or try the [`minimal`](../rules/minimal.md) ruleset which shows some warnings, but far fewer errors that would cause the lint to fail.

You can specify the ruleset with the `lint` command in Redocly CLI like this:
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Redocly uses rules to describe all the different aspects of API behavior that we
Rulesets are groups of rules that are applied together, and APIs can be checked against as many rulesets as needed during linting. To get you started, there are some built-in rulesets:

- Our [recommended](./rules/recommended.md) ruleset is our unabashedly opinionated recommendation of what we think a good API looks like. It's a great place to start, before adapting to your own context.
- A [recommended-strict](./rules/recommended.md#recommended-strict-ruleset) ruleset is identical to the `recommended`, except it elevates all warnings to errors. It's the ideal option for those who don't want to miss anything.
- A [minimal](./rules/minimal.md) ruleset is a good starting point for an existing API that doesn't currently conform to any standard. It has fewer rules that cause an error, with others either downgraded to a warning or turned off completely.

Enable a ruleset by adding a block like this to the Redocly configuration file:
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/minimal.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Warnings:
* [no-identical-paths](./no-identical-paths.md)
* [no-invalid-media-type-examples](./no-invalid-media-type-examples.md)
* [no-path-trailing-slash](./no-path-trailing-slash.md)
* [no-server-example-com](./no-server-example-com.md)
* [no-server-example.com](./no-server-example-com.md)
* [no-undefined-server-variable](./no-undefined-server-variable.md)
* [no-unused-components](./no-unused-components.md)
* [operation-2xx-response](./operation-2xx-response.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-server-trailing-slash.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ servers:
## Related rules
- [no-empty-servers](./no-empty-servers.md)
- [no-server-example-com](./no-server-example-com.md)
- [no-server-example.com](./no-server-example-com.md)
## Resources
Expand Down
6 changes: 5 additions & 1 deletion docs/rules/recommended.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ Warnings:
* [info-license](./info-license.md)
* [no-ambiguous-paths](./no-ambiguous-paths.md)
* [no-invalid-media-type-examples](./no-invalid-media-type-examples.md)
* [no-server-example-com](./no-server-example-com.md)
* [no-server-example.com](./no-server-example-com.md)
* [no-unused-components](./no-unused-components.md)
* [operation-2xx-response](./operation-2xx-response.md)
* [operation-4xx-response](./operation-4xx-response.md)
* [operation-operationId](./operation-operationId.md)
* [tag-description](./tag-description.md)

## Recommended strict ruleset

There is also a `recommended-strict` version of `recommended`, which elevates all warnings to errors.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

exports[`resolveConfig should ignore minimal from the root and read local file 1`] = `
{
"async2Decorators": {},
"async2Preprocessors": {},
"async2Rules": {
"channels-kebab-case": "off",
"no-channel-trailing-slash": "off",
},
"decorators": {},
"doNotResolveExamples": undefined,
"oas2Decorators": {},
Expand Down Expand Up @@ -100,6 +106,12 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1

exports[`resolveStyleguideConfig should resolve extends with local file config which contains path to nested config 1`] = `
{
"async2Decorators": {},
"async2Preprocessors": {},
"async2Rules": {
"channels-kebab-case": "off",
"no-channel-trailing-slash": "off",
},
"decorators": {},
"doNotResolveExamples": undefined,
"oas2Decorators": {},
Expand Down
31 changes: 30 additions & 1 deletion packages/core/src/config/__tests__/config-resolvers.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { colorize } from '../../logger';
import { Asserts, asserts } from '../../rules/common/assertions/asserts';
import { resolveStyleguideConfig, resolveApis, resolveConfig } from '../config-resolvers';
import recommended from '../recommended';

const path = require('path');

import type { StyleguideRawConfig, RawConfig } from '../types';
import type { StyleguideRawConfig, RawConfig, PluginStyleguideConfig } from '../types';

const configPath = path.join(__dirname, 'fixtures/resolve-config/redocly.yaml');
const baseStyleguideConfig: StyleguideRawConfig = {
Expand Down Expand Up @@ -220,6 +222,33 @@ describe('resolveStyleguideConfig', () => {
]);
expect(styleguide.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
});
it('should resolve `recommended-strict` ruleset correctly', async () => {
const expectedStrict = JSON.parse(
JSON.stringify(recommended)
) as PluginStyleguideConfig<'built-in'>;
for (const section of Object.values(expectedStrict)) {
for (let ruleName in section as any) {
// @ts-ignore
if (section[ruleName] === 'warn') {
// @ts-ignore
section[ruleName] = 'error';
}
// @ts-ignore
if (section[ruleName]?.severity === 'warn') {
// @ts-ignore
section[ruleName].severity = 'error';
}
}
}
const recommendedStrictPreset = JSON.parse(
JSON.stringify(
await resolveStyleguideConfig({
styleguideConfig: { extends: ['recommended-strict'] },
})
)
);
expect(recommendedStrictPreset).toMatchObject(expectedStrict);
});
});

describe('resolveApis', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/config/builtIn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import recommended from './recommended';
import recommendedStrict from './recommended-strict';
import all from './all';
import minimal from './minimal';
import { rules as oas3Rules } from '../rules/oas3';
Expand All @@ -13,6 +14,7 @@ import type { CustomRulesConfig, StyleguideRawConfig, Plugin } from './types';

export const builtInConfigs: Record<string, StyleguideRawConfig> = {
recommended,
'recommended-strict': recommendedStrict,
minimal,
all,
'redocly-registry': {
Expand Down
93 changes: 93 additions & 0 deletions packages/core/src/config/recommended-strict.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { PluginStyleguideConfig } from './types';

const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
rules: {
'info-contact': 'off',
'info-license': 'error',
'info-license-url': 'error',
'tag-description': 'error',
'tags-alphabetical': 'off',
'parameter-description': 'off',
'no-path-trailing-slash': 'error',
'no-identical-paths': 'error',
'no-ambiguous-paths': 'error',
'path-declaration-must-exist': 'error',
'path-not-include-query': 'error',
'path-parameters-defined': 'error',
'operation-description': 'off',
'operation-2xx-response': 'error',
'operation-4xx-response': 'error',
'operation-operationId': 'error',
'operation-summary': 'error',
'operation-operationId-unique': 'error',
'operation-operationId-url-safe': 'error',
'operation-parameters-unique': 'error',
'operation-tag-defined': 'off',
'security-defined': 'error',
'operation-singular-tag': 'off',
'no-unresolved-refs': 'error',
'no-enum-type-mismatch': 'error',
'paths-kebab-case': 'off',
spec: 'error',
'spec-strict-refs': 'off',
'no-http-verbs-in-paths': 'off',
'no-invalid-parameter-examples': 'off',
'no-invalid-schema-examples': 'off',
'path-excludes-patterns': 'off',
'path-http-verbs-order': 'off',
'path-params-defined': 'off',
'path-segment-plural': 'off',
'required-string-property-missing-min-length': 'off',
'response-contains-header': 'off',
'scalar-property-missing-example': 'off',
},
oas2Rules: {
'boolean-parameter-prefixes': 'off',
'request-mime-type': 'off',
'response-contains-property': 'off',
'response-mime-type': 'off',
},
oas3_0Rules: {
'no-invalid-media-type-examples': {
severity: 'error',
allowAdditionalProperties: false,
},
'no-server-example.com': 'error',
'no-server-trailing-slash': 'error',
'no-empty-servers': 'error',
'no-example-value-and-externalValue': 'error',
'no-unused-components': 'error',
'no-undefined-server-variable': 'error',
'no-server-variables-empty-enum': 'error',
'spec-components-invalid-map-name': 'error',
'boolean-parameter-prefixes': 'off',
'component-name-unique': 'off',
'operation-4xx-problem-details-rfc7807': 'off',
'request-mime-type': 'off',
'response-contains-property': 'off',
'response-mime-type': 'off',
},
oas3_1Rules: {
'no-invalid-media-type-examples': 'error',
'no-server-example.com': 'error',
'no-server-trailing-slash': 'error',
'no-empty-servers': 'error',
'no-example-value-and-externalValue': 'error',
'no-unused-components': 'error',
'no-undefined-server-variable': 'error',
'no-server-variables-empty-enum': 'error',
'spec-components-invalid-map-name': 'error',
'boolean-parameter-prefixes': 'off',
'component-name-unique': 'off',
'operation-4xx-problem-details-rfc7807': 'off',
'request-mime-type': 'off',
'response-contains-property': 'off',
'response-mime-type': 'off',
},
async2Rules: {
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
};

export default recommendedStrict;
5 changes: 4 additions & 1 deletion packages/core/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,17 @@ export type RulesFields =
| 'oas2Rules'
| 'oas3_0Rules'
| 'oas3_1Rules'
| 'async2Rules'
| 'preprocessors'
| 'oas2Preprocessors'
| 'oas3_0Preprocessors'
| 'oas3_1Preprocessors'
| 'async2Preprocessors'
| 'decorators'
| 'oas2Decorators'
| 'oas3_0Decorators'
| 'oas3_1Decorators';
| 'oas3_1Decorators'
| 'async2Decorators';

export enum AuthProviderType {
OIDC = 'OIDC',
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/config/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ function extractFlatConfig<
oas2Rules,
oas3_0Rules,
oas3_1Rules,
async2Rules,

preprocessors,
oas2Preprocessors,
oas3_0Preprocessors,
oas3_1Preprocessors,
async2Preprocessors,

decorators,
oas2Decorators,
oas3_0Decorators,
oas3_1Decorators,
async2Decorators,

...rawConfigRest
}: T): {
Expand All @@ -79,16 +82,19 @@ function extractFlatConfig<
oas2Rules,
oas3_0Rules,
oas3_1Rules,
async2Rules,

preprocessors,
oas2Preprocessors,
oas3_0Preprocessors,
oas3_1Preprocessors,
async2Preprocessors,

decorators,
oas2Decorators,
oas3_0Decorators,
oas3_1Decorators,
async2Decorators,

doNotResolveExamples: rawConfigRest.resolve?.doNotResolveExamples,
};
Expand Down Expand Up @@ -145,16 +151,19 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
oas2Rules: {},
oas3_0Rules: {},
oas3_1Rules: {},
async2Rules: {},

preprocessors: {},
oas2Preprocessors: {},
oas3_0Preprocessors: {},
oas3_1Preprocessors: {},
async2Preprocessors: {},

decorators: {},
oas2Decorators: {},
oas3_0Decorators: {},
oas3_1Decorators: {},
async2Decorators: {},

plugins: [],
pluginPaths: [],
Expand All @@ -175,6 +184,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_0Rules, rulesConf.rules || {});
Object.assign(result.oas3_1Rules, rulesConf.oas3_1Rules);
assignExisting(result.oas3_1Rules, rulesConf.rules || {});
Object.assign(result.async2Rules, rulesConf.async2Rules);
assignExisting(result.async2Rules, rulesConf.rules || {});

Object.assign(result.preprocessors, rulesConf.preprocessors);
Object.assign(result.oas2Preprocessors, rulesConf.oas2Preprocessors);
Expand All @@ -183,6 +194,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_0Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.oas3_1Preprocessors, rulesConf.oas3_1Preprocessors);
assignExisting(result.oas3_1Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.async2Preprocessors, rulesConf.async2Preprocessors);
assignExisting(result.async2Preprocessors, rulesConf.preprocessors || {});

Object.assign(result.decorators, rulesConf.decorators);
Object.assign(result.oas2Decorators, rulesConf.oas2Decorators);
Expand All @@ -191,6 +204,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_0Decorators, rulesConf.decorators || {});
Object.assign(result.oas3_1Decorators, rulesConf.oas3_1Decorators);
assignExisting(result.oas3_1Decorators, rulesConf.decorators || {});
Object.assign(result.async2Decorators, rulesConf.async2Decorators);
assignExisting(result.async2Decorators, rulesConf.decorators || {});

result.plugins!.push(...(rulesConf.plugins || []));
result.pluginPaths!.push(...(rulesConf.pluginPaths || []));
Expand Down

1 comment on commit 4620f79

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 76.12% 4054/5326
🟡 Branches 65.96% 2151/3261
🟡 Functions 68.26% 656/961
🟡 Lines 76.31% 3802/4982

Test suite run success

646 tests passing in 93 suites.

Report generated by 🧪jest coverage report action from 4620f79

Please sign in to comment.