Repository: asyncapi/parser-js
Pull request #911 (2023-11-27 11:40:50)

feat: add Spectral rule to validate operation messages
**Description**

Part of https://github.com/asyncapi/spec/issues/991

This PR adds all the scaffolding for adding Spectral rules to v3 plus [the first of three new rules](https://github.com/asyncapi/spec/issues/991#issuecomment-1825700075) related with https://github.com/asyncapi/spec/issues/991.
This rule, in particular, checks that operation messages, no matter where they are located, need to point to messages from the channel specified in its channel field.

**Related issue(s)**
https://github.com/asyncapi/spec/issues/991

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=911)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=911)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=911)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=911&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=911&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/src/ruleset/index.ts b/src/ruleset/index.ts
index f80a62f..b4197dc 100644
--- a/src/ruleset/index.ts
+++ b/src/ruleset/index.ts
@@ -1,5 +1,6 @@
 import { coreRuleset, recommendedRuleset } from './ruleset';
 import { v2CoreRuleset, v2SchemasRuleset, v2RecommendedRuleset } from './v2';
+import { v3CoreRuleset } from './v3';
 
 import type { Parser } from '../parser';
 import type { RulesetDefinition } from '@stoplight/spectral-core';
@@ -18,6 +19,7 @@ export function createRuleset(parser: Parser, options?: RulesetOptions): Ruleset
     useCore && v2CoreRuleset,
     useCore && v2SchemasRuleset(parser),
     useRecommended && v2RecommendedRuleset,
+    useCore && v3CoreRuleset,
     ...(options as any || {})?.extends || [],
   ].filter(Boolean);
 
diff --git a/src/ruleset/v3/functions/operationMessagesUnambiguity.ts b/src/ruleset/v3/functions/operationMessagesUnambiguity.ts
new file mode 100644
index 0000000..75c1d52
--- /dev/null
+++ b/src/ruleset/v3/functions/operationMessagesUnambiguity.ts
@@ -0,0 +1,44 @@
+import { createRulesetFunction } from '@stoplight/spectral-core';
+import type { IFunctionResult } from '@stoplight/spectral-core';
+import { SchemaDefinition } from '@stoplight/spectral-core/dist/ruleset/function';
+
+const referenceSchema: SchemaDefinition = {
+  type: 'object',
+  properties: {
+    $ref: {
+      type: 'string',
+      format: 'uri-reference'
+    },
+  },
+};
+
+export const operationMessagesUnambiguity = createRulesetFunction<{ channel?: {'$ref': string}; messages?: [{'$ref': string}] }, null>(
+  {
+    input: {
+      type: 'object',
+      properties: {
+        channel: referenceSchema,
+        messages: {
+          type: 'array',
+          items: referenceSchema,
+        },
+      },
+    },
+    options: null,
+  },
+  (targetVal, _, ctx) => {
+    const results: IFunctionResult[] = [];
+    const channelPointer = targetVal.channel?.$ref as string; // required
+
+    targetVal.messages?.forEach((message, index) => {      
+      if (!message.$ref.startsWith(`${channelPointer}/messages`)) {
+        results.push({
+          message: 'Operation message does not belong to the specified channel.',
+          path: [...ctx.path, 'messages', index],
+        });
+      }
+    });
+  
+    return results;
+  },
+);
diff --git a/src/ruleset/v3/index.ts b/src/ruleset/v3/index.ts
new file mode 100644
index 0000000..5acc418
--- /dev/null
+++ b/src/ruleset/v3/index.ts
@@ -0,0 +1,1 @@
+export * from './ruleset';
diff --git a/src/ruleset/v3/ruleset.ts b/src/ruleset/v3/ruleset.ts
new file mode 100644
index 0000000..56f7b68
--- /dev/null
+++ b/src/ruleset/v3/ruleset.ts
@@ -0,0 +1,28 @@
+/* eslint-disable sonarjs/no-duplicate-string */
+
+import { AsyncAPIFormats } from '../formats';
+import { operationMessagesUnambiguity } from './functions/operationMessagesUnambiguity';
+
+export const v3CoreRuleset = {
+  description: 'Core AsyncAPI 3.x.x ruleset.',
+  formats: AsyncAPIFormats.filterByMajorVersions(['3']).formats(),
+  rules: {
+    /**
+     * Operation Object rules
+     */
+    'asyncapi3-operation-messages-from-referred-channel': {
+      description: 'Operation "messages" must be a subset of the messages defined in the channel referenced in this operation.',
+      message: '{{error}}',
+      severity: 'error',
+      recommended: true,
+      resolved: false, // We use the JSON pointer to match the channel.
+      given: [
+        '$.operations.*',
+        '$.components.operations.*',
+      ],
+      then: {
+        function: operationMessagesUnambiguity,
+      },
+    },
+  },
+};


Test patch:
diff --git a/test/custom-operations/parse-schema-v3.spec.ts b/test/custom-operations/parse-schema-v3.spec.ts
index fab592b..6e56ed1 100644
--- a/test/custom-operations/parse-schema-v3.spec.ts
+++ b/test/custom-operations/parse-schema-v3.spec.ts
@@ -26,7 +26,7 @@ describe('custom operations for v3 - parse schemas', function() {
           },
           messages: [
             {
-              $ref: '#/components/messages/message'
+              $ref: '#/channels/channel/messages/message'
             }
           ]
         }
diff --git a/test/ruleset/rules/v3/asyncapi3-operation-messages-from-referred-channel.spec.ts b/test/ruleset/rules/v3/asyncapi3-operation-messages-from-referred-channel.spec.ts
new file mode 100644
index 0000000..fc51d47
--- /dev/null
+++ b/test/ruleset/rules/v3/asyncapi3-operation-messages-from-referred-channel.spec.ts
@@ -0,0 +1,323 @@
+import { testRule, DiagnosticSeverity } from '../../tester';
+
+testRule('asyncapi3-operation-messages-from-referred-channel', [
+  {
+    name: 'valid case - required channel',
+    document: {
+      asyncapi: '3.0.0',
+      info: {
+        title: 'Account Service',
+        version: '1.0.0'
+      },
+      channels: {
+        UserSignedUp: {
+          messages: {
+            UserSignedUp: {
+              payload: {
+                type: 'object',
+                properties: {
+                  displayName: {
+                    type: 'string'
+                  },
+                  email: {
+                    type: 'string'
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      operations: {
+        UserSignedUp: {
+          action: 'send',
+          channel: {
+            $ref: '#/channels/UserSignedUp'
+          },
+          messages: [
+            {
+              $ref: '#/channels/UserSignedUp/messages/UserSignedUp'
+            }
+          ]
+        }
+      }
+    },
+    errors: [],
+  },
+  {
+    name: 'valid case - optional channel',
+    document: {
+      asyncapi: '3.0.0',
+      info: {
+        title: 'Account Service',
+        version: '1.0.0'
+      },
+      channels: {
+        UserSignedUp: {
+          messages: {
+            UserSignedUp: {
+              payload: {
+                type: 'object',
+                properties: {
+                  displayName: {
+                    type: 'string'
+                  },
+                  email: {
+                    type: 'string'
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      components: {
+        operations: {
+          UserSignedUp: {
+            action: 'send',
+            channel: {
+              $ref: '#/channels/UserSignedUp'
+            },
+            messages: [
+              {
+                $ref: '#/channels/UserSignedUp/messages/UserSignedUp'
+              }
+            ]
+          }
+        },
+      }
+    },
+    errors: [],
+  },
+  {
+    name: 'invalid case - message from operation in root pointing to a message from an optional channel (same name) defined under components',
+    document: {
+      asyncapi: '3.0.0',
+      info: {
+        title: 'Account Service',
+        version: '1.0.0'
+      },
+      channels: {
+        UserSignedUp: {
+          messages: {
+            UserSignedUp: {
+              payload: {
+                type: 'object',
+                properties: {
+                  displayName: {
+                    type: 'string'
+                  },
+                  email: {
+                    type: 'string'
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      operations: {
+        UserSignedUp: {
+          action: 'send',
+          channel: {
+            $ref: '#/channels/UserSignedUp'
+          },
+          messages: [
+            {
+              $ref: '#/components/channels/UserSignedUp/messages/UserSignedUp'
+            }
+          ]
+        }
+      },
+      components: {
+        channels: {
+          UserSignedUp: {
+            messages: {
+              UserSignedUp: {
+                payload: {
+                  type: 'object',
+                  properties: {
+                    displayName: {
+                      type: 'string'
+                    },
+                    email: {
+                      type: 'string'
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+      },
+    },
+    errors: [
+      {
+        message: 'Operation message does not belong to the specified channel.',
+        path: ['operations', 'UserSignedUp', 'messages', '0'],
+        severity: DiagnosticSeverity.Error,
+      }
+    ],
+  },
+  {
+    name: 'invalid case - message from operation in components pointing to a message from a different channel defined under components',
+    document: {
+      asyncapi: '3.0.0',
+      info: {
+        title: 'Account Service',
+        version: '1.0.0'
+      },
+      channels: {
+        UserSignedUp: {
+          messages: {
+            UserSignedUp: {
+              payload: {
+                type: 'object',
+                properties: {
+                  displayName: {
+                    type: 'string'
+                  },
+                  email: {
+                    type: 'string'
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      components: {
+        channels: {
+          UserRemoved: {
+            messages: {
+              UserRemoved: {
+                payload: {
+                  type: 'object',
+                  properties: {
+                    displayName: {
+                      type: 'string'
+                    },
+                    email: {
+                      type: 'string'
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        operations: {
+          UserSignedUp: {
+            action: 'send',
+            channel: {
+              $ref: '#/channels/UserSignedUp'
+            },
+            messages: [
+              {
+                $ref: '#/components/channels/UserRemoved/messages/UserRemoved'
+              }
+            ]
+          }
+        },
+      }
+    },
+    errors: [
+      {
+        message: 'Operation message does not belong to the specified channel.',
+        path: ['components', 'operations', 'UserSignedUp', 'messages', '0'],
+        severity: DiagnosticSeverity.Error,
+      }
+    ],
+  },
+  {
+    name: 'invalid case - multiple messages from operation in components pointing to multiple message from a different channel defined under components',
+    document: {
+      asyncapi: '3.0.0',
+      info: {
+        title: 'Account Service',
+        version: '1.0.0'
+      },
+      channels: {
+        UserSignedUp: {
+          messages: {
+            UserSignedUp: {
+              payload: {
+                type: 'object',
+                properties: {
+                  displayName: {
+                    type: 'string'
+                  },
+                  email: {
+                    type: 'string'
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      components: {
+        channels: {
+          UserRemoved: {
+            messages: {
+              UserRemoved: {
+                payload: {
+                  type: 'object',
+                  properties: {
+                    displayName: {
+                      type: 'string'
+                    },
+                    email: {
+                      type: 'string'
+                    }
+                  }
+                }
+              },
+              UserDeleted: {
+                payload: {
+                  type: 'object',
+                  properties: {
+                    displayName: {
+                      type: 'string'
+                    },
+                    email: {
+                      type: 'string'
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        operations: {
+          UserSignedUp: {
+            action: 'send',
+            channel: {
+              $ref: '#/channels/UserSignedUp'
+            },
+            messages: [
+              {
+                $ref: '#/components/channels/UserRemoved/messages/UserRemoved'
+              },
+              {
+                $ref: '#/components/channels/UserRemoved/messages/UserDeleted'
+              }
+            ]
+          }
+        },
+      }
+    },
+    errors: [
+      {
+        message: 'Operation message does not belong to the specified channel.',
+        path: ['components', 'operations', 'UserSignedUp', 'messages', '0'],
+        severity: DiagnosticSeverity.Error,
+      },
+      {
+        message: 'Operation message does not belong to the specified channel.',
+        path: ['components', 'operations', 'UserSignedUp', 'messages', '1'],
+        severity: DiagnosticSeverity.Error,
+      }
+    ],
+  },
+]);
diff --git a/test/ruleset/tester.ts b/test/ruleset/tester.ts
index df08292..65c9971 100644
--- a/test/ruleset/tester.ts
+++ b/test/ruleset/tester.ts
@@ -4,6 +4,7 @@ import { AvroSchemaParser } from '@asyncapi/avro-schema-parser'; // allows testi
 // rulesets
 import { coreRuleset, recommendedRuleset } from '../../src/ruleset/ruleset';
 import { v2CoreRuleset, v2SchemasRuleset, v2RecommendedRuleset } from '../../src/ruleset/v2';
+import { v3CoreRuleset } from '../../src/ruleset/v3';
 
 import type { ParserOptions } from '../../src/parser';
 import type { IRuleResult, RulesetDefinition } from '@stoplight/spectral-core';
@@ -15,6 +16,7 @@ type RuleNames =
   | RulesetRules<typeof v2CoreRuleset> 
   | RulesetRules<typeof v2RecommendedRuleset>
   | RulesetRules<ReturnType<typeof v2SchemasRuleset>>
+  | RulesetRules<typeof v3CoreRuleset> 
 
 type Scenario = ReadonlyArray<
   Readonly<{
@@ -51,6 +53,7 @@ function createParser(rules: Array<RuleNames>, options: ParserOptions = {}): Par
       [recommendedRuleset as RulesetDefinition, 'off'],
       [v2CoreRuleset as RulesetDefinition, 'off'],
       [v2RecommendedRuleset as RulesetDefinition, 'off'],
+      [v3CoreRuleset as RulesetDefinition, 'off'],
     ],
     rules: {
       'asyncapi2-schemas': 'off',