Repository: Redocly/redocly-cli
Pull request #941 (2022-11-16 16:38:19)

docs: improve the changelog
## What/Why/How?

Minor changelog improvements




Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index bef96881..9c1b5a52 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,20 +5,26 @@ toc:
 
 # Redocly CLI changelog
 
+## 1.0.0-beta.114 (2022-11-18)
+
+### Features
+
+- Added a new assertion [`notPattern`](./rules/custom-rules.md#notpattern-example) to the custom rules.
+
 ## 1.0.0-beta.113 (2022-11-15)
 
 ### Changes
 
-- Added missing OAS2 and OAS3 list types.
-- Removed the feature where we add the `recommended` ruleset fallback when there is a config defined.
-- Now we don't show an error when discriminator is used with `allOf` keyword.
+- Removed automatically adding the `recommended` configuration when there is a config defined without an `extends` list.
 
 ### Fixes
 
 - Fixed an issue with undefined `process.cwd` in browser environment.
 - Fixed an issue with `$anchors` in OpenAPI documents are not properly parsed.
 - Fixed an issue with the `spec` rule not reporting on `nullable` in Schema object that don't have a `type` sibling.
-
+- Added missing OAS2 and OAS3 list types.
+- Don't show false media type example errors when a discriminator is used with the `allOf` keyword.
+ 
 ## 1.0.0-beta.112 (2022-11-01)
 
 ### Changes
diff --git a/docs/rules/custom-rules.md b/docs/rules/custom-rules.md
index ee98d6af..2ab7ff2c 100644
--- a/docs/rules/custom-rules.md
+++ b/docs/rules/custom-rules.md
@@ -65,6 +65,7 @@ maxLength | integer | Asserts a maximum length (exclusive) of a string or list (
 minLength | integer | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example).
 nonEmpty | boolean | Asserts a property is not empty. See [nonEmpty example](#nonempty-example).
 pattern | string | Asserts a value matches a regex pattern. See [regex pattern example](#pattern-example).
+notPattern | string | Asserts a value doesn't match a regex pattern. See [regex notPattern example](#notpattern-example).
 mutuallyExclusive | [string] | Asserts that listed properties (key names only) are mutually exclusive. See [mutuallyExclusive example](#mutuallyexclusive-example).
 mutuallyRequired | [string] | Asserts that listed properties (key names only) are mutually required. See [mutuallyRequired example](#mutuallyrequired-example).
 ref | boolean \| string | Asserts a reference object presence in object's property. A boolean value of `true` means the property has a `$ref` defined. A boolean value of `false` means the property has not defined a `$ref` (it has an in-place value). A string value means that the `$ref` is defined and the unresolved value must match the pattern (for example, `'/paths\/. *\.yaml$/'`). See [ref example](#ref-example).|
@@ -381,6 +382,20 @@ rules:
       pattern: /test/
 ```
 
+### `notPattern` example
+
+The following example asserts that the operation summary doesn't start with "The".
+
+```yaml
+rules:
+  assert/operation-summary-contains-test:
+    subject:
+      type: Operation
+      property: The summary
+    assertions:
+      notPattern: /^The/
+```
+
 ### `casing` example
 
 The following example asserts the casing style is `PascalCase` for `NamedExamples` map keys.
diff --git a/package-lock.json b/package-lock.json
index d7711c0a..f8cb2bc1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@redocly/cli",
-  "version": "1.0.0-beta.113",
+  "version": "1.0.0-beta.114",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@redocly/cli",
-      "version": "1.0.0-beta.113",
+      "version": "1.0.0-beta.114",
       "license": "MIT",
       "workspaces": [
         "packages/*"
@@ -9750,10 +9750,10 @@
     },
     "packages/cli": {
       "name": "@redocly/cli",
-      "version": "1.0.0-beta.113",
+      "version": "1.0.0-beta.114",
       "license": "MIT",
       "dependencies": {
-        "@redocly/openapi-core": "1.0.0-beta.113",
+        "@redocly/openapi-core": "1.0.0-beta.114",
         "assert-node-version": "^1.0.3",
         "chokidar": "^3.5.1",
         "colorette": "^1.2.0",
@@ -9798,6 +9798,11 @@
       "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
       "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
     },
+    "packages/cli/node_modules/@types/node": {
+      "version": "14.18.33",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz",
+      "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg=="
+    },
     "packages/cli/node_modules/@types/yargs": {
       "version": "16.0.2",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.2.tgz",
@@ -9857,7 +9862,7 @@
     },
     "packages/core": {
       "name": "@redocly/openapi-core",
-      "version": "1.0.0-beta.113",
+      "version": "1.0.0-beta.114",
       "license": "MIT",
       "dependencies": {
         "@redocly/ajv": "^8.11.0",
@@ -10850,7 +10855,7 @@
     "@redocly/cli": {
       "version": "file:packages/cli",
       "requires": {
-        "@redocly/openapi-core": "1.0.0-beta.113",
+        "@redocly/openapi-core": "1.0.0-beta.114",
         "@types/configstore": "^5.0.1",
         "@types/react": "^17.0.8",
         "@types/react-dom": "^17.0.5",
@@ -10886,6 +10891,10 @@
           "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
           "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
         },
+        "@types/node": {
+          "version": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz",
+          "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg=="
+        },
         "@types/yargs": {
           "version": "16.0.2",
           "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.2.tgz",
diff --git a/package.json b/package.json
index 0f5284af..fca10926 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@redocly/cli",
-  "version": "1.0.0-beta.113",
+  "version": "1.0.0-beta.114",
   "description": "",
   "private": true,
   "engines": {
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 4d984d5f..a9b44d67 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@redocly/cli",
-  "version": "1.0.0-beta.113",
+  "version": "1.0.0-beta.114",
   "description": "",
   "license": "MIT",
   "bin": {
@@ -33,7 +33,7 @@
     "Roman Hotsiy <roman@redoc.ly> (https://redoc.ly/)"
   ],
   "dependencies": {
-    "@redocly/openapi-core": "1.0.0-beta.113",
+    "@redocly/openapi-core": "1.0.0-beta.114",
     "assert-node-version": "^1.0.3",
     "chokidar": "^3.5.1",
     "colorette": "^1.2.0",
diff --git a/packages/core/package.json b/packages/core/package.json
index 091df9e3..fee6a3d0 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@redocly/openapi-core",
-  "version": "1.0.0-beta.113",
+  "version": "1.0.0-beta.114",
   "description": "",
   "main": "lib/index.js",
   "engines": {
diff --git a/packages/core/src/rules/common/assertions/asserts.ts b/packages/core/src/rules/common/assertions/asserts.ts
index 58a9b5e7..e0d80105 100644
--- a/packages/core/src/rules/common/assertions/asserts.ts
+++ b/packages/core/src/rules/common/assertions/asserts.ts
@@ -18,6 +18,7 @@ export type AssertionFn = (
 
 export type Asserts = {
   pattern: AssertionFn;
+  notPattern: AssertionFn;
   enum: AssertionFn;
   defined: AssertionFn;
   required: AssertionFn;
@@ -40,6 +41,7 @@ export const runOnKeysSet = new Set<keyof Asserts>([
   'mutuallyRequired',
   'enum',
   'pattern',
+  'notPattern',
   'minLength',
   'maxLength',
   'casing',
@@ -53,6 +55,7 @@ export const runOnKeysSet = new Set<keyof Asserts>([
 ]);
 export const runOnValuesSet = new Set<keyof Asserts>([
   'pattern',
+  'notPattern',
   'enum',
   'defined',
   'undefined',
@@ -69,18 +72,33 @@ export const asserts: Asserts = {
   pattern: (value: string | string[], condition: string, baseLocation: Location) => {
     if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
     const values = runOnValue(value) ? [value] : value;
-    const regx = regexFromString(condition);
+    const regex = regexFromString(condition);
 
     return values
       .map(
         (_val) =>
-          !regx?.test(_val) && {
+          !regex?.test(_val) && {
             message: `"${_val}" should match a regex ${condition}`,
             location: runOnValue(value) ? baseLocation : baseLocation.key(),
           }
       )
       .filter(isTruthy);
   },
+  notPattern: (value: string | string[], condition: string, baseLocation: Location) => {
+    if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
+    const values = runOnValue(value) ? [value] : value;
+    const regex = regexFromString(condition);
+
+    return values
+      .map(
+        (_val) =>
+          regex?.test(_val) && {
+            message: `"${_val}" should not match a regex ${condition}`,
+            location: runOnValue(value) ? baseLocation : baseLocation.key(),
+          }
+      )
+      .filter(isTruthy);
+  },
   enum: (value: string | string[], condition: string[], baseLocation: Location) => {
     if (typeof value === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
     const values = runOnValue(value) ? [value] : value;
diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts
index 9ce67ac5..a50ce4ad 100644
--- a/packages/core/src/types/redocly-yaml.ts
+++ b/packages/core/src/types/redocly-yaml.ts
@@ -258,6 +258,7 @@ const AssertionDefinitionAssertions: NodeType = {
   properties: {
     enum: { type: 'array', items: { type: 'string' } },
     pattern: { type: 'string' },
+    notPattern: { type: 'string' },
     casing: {
       enum: [
         'camelCase',


Test patch:
diff --git a/__tests__/lint/assertions-pattern-error/.redocly.yaml b/__tests__/lint/assertions-pattern-error/.redocly.yaml
index 4bfa24bb..6d7c790d 100644
--- a/__tests__/lint/assertions-pattern-error/.redocly.yaml
+++ b/__tests__/lint/assertions-pattern-error/.redocly.yaml
@@ -4,9 +4,9 @@ apis:
 
 rules:
   assert/summary-pattern:
-    subject: 
+    subject:
       type: Operation
       property: summary
-    message: Operation summary should have 'test' word
-    assertions: 
+    assertions:
       pattern: '/test/'
+      notPattern: /summary/
diff --git a/__tests__/lint/assertions-pattern-error/snapshot.js b/__tests__/lint/assertions-pattern-error/snapshot.js
index 515db645..d4b5c1a0 100644
--- a/__tests__/lint/assertions-pattern-error/snapshot.js
+++ b/__tests__/lint/assertions-pattern-error/snapshot.js
@@ -5,7 +5,21 @@ exports[`E2E lint assertions-pattern-error 1`] = `
 validating /openapi.yaml...
 [1] openapi.yaml:20:16 at #/paths/~1pet~1findByStatus/get/summary
 
-Operation summary should have 'test' word
+summary-pattern assertion failed because the Operation summary didn't meet the assertions: "summary example" should match a regex /test/
+
+18 | get:
+19 |   operationId: example
+20 |   summary: summary example
+   |            ^^^^^^^^^^^^^^^
+21 |   tags:
+22 |     - foo
+
+Error was generated by the summary-pattern assertion rule.
+
+
+[2] openapi.yaml:20:16 at #/paths/~1pet~1findByStatus/get/summary
+
+summary-pattern assertion failed because the Operation summary didn't meet the assertions: "summary example" should not match a regex /summary/
 
 18 | get:
 19 |   operationId: example
@@ -19,7 +33,7 @@ Error was generated by the summary-pattern assertion rule.
 
 /openapi.yaml: validated in <test>ms
 
-❌ Validation failed with 1 error.
+❌ Validation failed with 2 errors.
 run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
 
 
diff --git a/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts b/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts
index acaefa8a..7a879317 100644
--- a/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts
+++ b/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts
@@ -43,6 +43,24 @@ describe('oas3 assertions', () => {
       });
     });
 
+    describe('notPattern', () => {
+      it('value should not match regex pattern', () => {
+        expect(asserts.notPattern('test string', '/test me/', baseLocation)).toEqual([]);
+        expect(asserts.notPattern('test string', '/test/', baseLocation)).toEqual([
+          { location: baseLocation, message: '"test string" should not match a regex /test/' },
+        ]);
+        expect(
+          asserts.notPattern(['test string', 'test me'], '/test other/', baseLocation)
+        ).toEqual([]);
+        expect(asserts.notPattern(['test string', 'test me'], '/test me/', baseLocation)).toEqual([
+          {
+            message: '"test me" should not match a regex /test me/',
+            location: baseLocation.key(),
+          },
+        ]);
+      });
+    });
+
     describe('ref', () => {
       it('value should have ref', () => {
         expect(asserts.ref({ $ref: 'text' }, true, baseLocation, { $ref: 'text' })).toEqual([]);