Repository: Redocly/redocly-cli
Pull request #675 (2022-05-06 12:01:27)

feat: ref assertion
## What/Why/How?
Added a new assertation that checks if ref is present.

Add to the `Assertion Object`:
| Property | Type | Description |
|---|---|---|
|`ref`| string \| boolean | Evaluates the subject'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$/'`).    | 

Example with pattern

```yaml
subject: PathItem
ref:  '/paths\/.*\.yaml$/'
```
Example with defined
```yaml
subject: PathItem
ref: true
```

**Also**, added a small related fix to _pattern_ assert, because it failed on complicated regexes (e.g `/^(\.\/)?components\/.*\.yaml$/`)
## Reference
#648 
## Testing
Added unit tests
## Screenshots (optional)

## Check yourself

- [x] Code is linted
- [x] Tested with redoc/reference-docs/workflows
- [x] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


minalabib223: في جمعة، ٦ مايو، ٢٠٢٢ في ٥:٤٤ م، كتب Adam Altman ***@***.***>:

> ***@***.**** commented on this pull request.
> ------------------------------
>
> In packages/core/src/rules/common/assertions/utils.ts
> <https://github.com/Redocly/openapi-cli/pull/675#discussion_r866900528>:
>
> > @@ -165,3 +166,8 @@ function runAssertion(
>      });
>    }
>  }
> +
> +export function regexFromString(input: string): RegExp | null {
> +  const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
>
> Thank you
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/Redocly/openapi-cli/pull/675#discussion_r866900528>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AW4X6GBM35BRKFRND7GD3ADVIUV5JANCNFSM5VH6JN2A>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>

minalabib223: في جمعة، ٦ مايو، ٢٠٢٢ في ٥:٥٣ م، كتب minalabib223 ***@***.***
>:

> في جمعة، ٦ مايو، ٢٠٢٢ في ٥:٤٤ م، كتب Adam Altman ***@***.***>:
>
> > ***@***.**** commented on this pull request.
> > ------------------------------
> >
> > In packages/core/src/rules/common/assertions/utils.ts
> > <https://github.com/Redocly/openapi-cli/pull/675#discussion_r866900528>:
> >
> > > @@ -165,3 +166,8 @@ function runAssertion(
> > });
> > }
> > }
> > +
> > +export function regexFromString(input: string): RegExp | null {
> > + const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
> >
> > Thank you
> >
> > —
> > Reply to this email directly, view it on GitHub
> > <https://github.com/Redocly/openapi-cli/pull/675#discussion_r866900528>,
> > or unsubscribe
> > <
> https://github.com/notifications/unsubscribe-auth/AW4X6GBM35BRKFRND7GD3ADVIUV5JANCNFSM5VH6JN2A
> >
> > .
> > You are receiving this because you commented.Message ID:
> > ***@***.***>
> >
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/Redocly/redocly-cli/pull/675#issuecomment-1119708991>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AW4X6GHVCIUQR5WDFHTCPT3VIUW73ANCNFSM5VH6JN2A>
> .
> You are receiving this because you are subscribed to this thread.Message
> ID: ***@***.***>
>

And678: @RomanHotsiy Added e2e tests (for true, false and regex asserts), could you check them, the functionality seems to work there.
Can you send your .redocly.yaml/openapi.yaml that doesn't work with this assertion?
Thanks 
RomanHotsiy: @And678 I tried case from this PR description and it doesn't work. Add tests for the case when there is no `property`:

![image](https://user-images.githubusercontent.com/3975738/168600677-03b41561-a15b-41d8-b321-691781c466b6.png)

adamaltman: @And678 how's it going? When do you think this will be ready to review again?
And678: @adamaltman Doing it today, will be ready for review soon, didn't do it yesterday because i had to be ooo
github-actions[bot]: <!-- jest coverage report action at  -->

## Coverage report 


### Total coverage

| Status | Category   | Percentage | Covered / Total |
| :----: | :--------- | :--------- | :-------------: |
|   🟡   | Statements | 65.92%     |    2646/4014    |
|   🔴   | Branches   | 59.99%     |    1459/2432    |
|   🔴   | Functions  | 57.97%     |     422/728     |
|   🟡   | Lines      | 66.18%     |    2448/3699    |

> Status of coverage: 🟢 - ok, 🟡 - slightly more than threshold, 🔴 - under the threshold




<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from 8ba3ac0cd241003727a767a57102f242f8a4313a</p>

tatomyr: I've configured reporting on key/values from inside the `asserts` object methods depending on the context. 
Please look at the updated snapshots and examine whether the highlightings are proper.

Patch:
diff --git a/.prettierrc.json b/.prettierrc.json
index 5e2863a1..5ac85e27 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,5 +1,4 @@
 {
   "printWidth": 100,
-  "singleQuote": true,
-  "trailingComma": "all"
+  "singleQuote": true
 }
diff --git a/docs/resources/rules/assertions.md b/docs/resources/rules/assertions.md
index e73d666e..a71b2fbf 100644
--- a/docs/resources/rules/assertions.md
+++ b/docs/resources/rules/assertions.md
@@ -43,6 +43,7 @@ undefined | `boolean` | Asserts a property is undefined. See [undefined example]
 nonEmpty | `boolean` | Asserts a property is not empty. See [nonEmpty example](#nonempty-example).
 minLength | `integer` | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example).
 maxLength | `integer` | Asserts a maximum length (exclusive) of a string or list (array). See [maxLength example](#maxlength-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).|
 
 ## Context object
 
@@ -435,7 +436,35 @@ lint:
       severity: error
       maxLength: 20
 ```
+
+### `ref` example
+
+The following example asserts that schema in MediaType contains a Reference object ($ref).
+
+```yaml
+lint:
+  rules:
+    assert/mediatype-schema-has-ref:
+      - subject: MediaType
+        property: schema
+        message: Ref is required.
+        ref: true
+```
+
+Also, you can specify a Regular Expression to check if the reference object conforms to it:
+
+```yaml
+lint:
+  rules:
+    assert/mediatype-schema-ref-pattern:
+      - subject: MediaType
+        property: schema
+        message: Ref needs to point to components directory.
+        ref: /^(\.\/)?components\/.*\.yaml$/
+```
+
 Redocly CLI
+
 ## OpenAPI node types
 
 Redocly defines a type tree based on the document type.
diff --git a/jest.config.js b/jest.config.js
index 9cff8243..c00e8dfe 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -13,14 +13,14 @@ module.exports = {
     'packages/core/': {
       statements: 77,
       branches: 69,
-      functions: 65,
+      functions: 66,
       lines: 77,
     },
     'packages/cli/': {
-      statements: 30,
-      branches: 27,
+      statements: 33,
+      branches: 28,
       functions: 29,
-      lines: 32,
+      lines: 34,
     },
   },
   testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'],
diff --git a/packages/core/src/rules/common/assertions/asserts.ts b/packages/core/src/rules/common/assertions/asserts.ts
index b40512ca..199b00a3 100644
--- a/packages/core/src/rules/common/assertions/asserts.ts
+++ b/packages/core/src/rules/common/assertions/asserts.ts
@@ -1,6 +1,18 @@
-import { OrderOptions, OrderDirection, isOrdered, getIntersectionLength } from './utils';
+import { Location } from '../../../ref-utils';
+import { isString as runOnValue } from '../../../utils';
+import {
+  OrderOptions,
+  OrderDirection,
+  isOrdered,
+  getIntersectionLength,
+  regexFromString,
+} from './utils';
 
-type Asserts = Record<string, (value: any, condition: any) => boolean>;
+type AssertResult = { isValid: boolean; location?: Location };
+type Asserts = Record<
+  string,
+  (value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult
+>;
 
 export const runOnKeysSet = new Set([
   'mutuallyExclusive',
@@ -13,6 +25,8 @@ export const runOnKeysSet = new Set([
   'sortOrder',
   'disallowed',
   'required',
+  'requireAny',
+  'ref',
 ]);
 export const runOnValuesSet = new Set([
   'pattern',
@@ -24,73 +38,82 @@ export const runOnValuesSet = new Set([
   'maxLength',
   'casing',
   'sortOrder',
+  'ref',
 ]);
 
 export const asserts: Asserts = {
-  pattern: (value: string | string[], condition: string): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    const values = typeof value === 'string' ? [value] : value;
-    const regexOptions = condition.match(/(\b\/\b)(.+)/g) || ['/'];
-    condition = condition.slice(1).replace(regexOptions[0], '');
-    const regx = new RegExp(condition, regexOptions[0].slice(1));
+  pattern: (value: string | string[], condition: string, baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    const values = runOnValue(value) ? [value] : value;
+    const regx = regexFromString(condition);
     for (let _val of values) {
-      if (!_val.match(regx)) {
-        return false;
+      if (!regx?.test(_val)) {
+        return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
       }
     }
-    return true;
+    return { isValid: true };
   },
-  enum: (value: string | string[], condition: string[]): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    const values = typeof value === 'string' ? [value] : value;
+  enum: (value: string | string[], condition: string[], baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    const values = runOnValue(value) ? [value] : value;
     for (let _val of values) {
       if (!condition.includes(_val)) {
-        return false;
+        return {
+          isValid: false,
+          location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
+        };
       }
     }
-    return true;
+    return { isValid: true };
   },
-  defined: (value: string | undefined, condition: boolean = true): boolean => {
+  defined: (value: string | undefined, condition: boolean = true, baseLocation: Location) => {
     const isDefined = typeof value !== 'undefined';
-    return condition ? isDefined : !isDefined;
+    return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
   },
-  required: (value: string[], keys: string[]): boolean => {
+  required: (value: string[], keys: string[], baseLocation: Location) => {
     for (const requiredKey of keys) {
       if (!value.includes(requiredKey)) {
-        return false;
+        return { isValid: false, location: baseLocation.key() };
       }
     }
-    return true;
+    return { isValid: true };
   },
-  disallowed: (value: string | string[], condition: string[]): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    const values = typeof value === 'string' ? [value] : value;
+  disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    const values = runOnValue(value) ? [value] : value;
     for (let _val of values) {
       if (condition.includes(_val)) {
-        return false;
+        return {
+          isValid: false,
+          location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
+        };
       }
     }
-    return true;
+    return { isValid: true };
   },
-  undefined: (value: any, condition: boolean = true): boolean => {
+  undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
     const isUndefined = typeof value === 'undefined';
-    return condition ? isUndefined : !isUndefined;
+    return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
   },
-  nonEmpty: (value: string | undefined | null, condition: boolean = true): boolean => {
+  nonEmpty: (
+    value: string | undefined | null,
+    condition: boolean = true,
+    baseLocation: Location
+  ) => {
     const isEmpty = typeof value === 'undefined' || value === null || value === '';
-    return condition ? !isEmpty : isEmpty;
+    return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
   },
-  minLength: (value: string | any[], condition: number): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    return value.length >= condition;
+  minLength: (value: string | any[], condition: number, baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    return { isValid: value.length >= condition, location: baseLocation };
   },
-  maxLength: (value: string | any[], condition: number): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    return value.length <= condition;
+  maxLength: (value: string | any[], condition: number, baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    return { isValid: value.length <= condition, location: baseLocation };
   },
-  casing: (value: string | string[], condition: string): boolean => {
-    if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
-    const values = typeof value === 'string' ? [value] : value;
+  casing: (value: string | string[], condition: string, baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    const values: string[] = runOnValue(value) ? [value] : value;
     for (let _val of values) {
       let matchCase = false;
       switch (condition) {
@@ -117,24 +140,46 @@ export const asserts: Asserts = {
           break;
       }
       if (!matchCase) {
-        return false;
+        return {
+          isValid: false,
+          location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
+        };
       }
     }
-    return true;
+    return { isValid: true };
   },
-  sortOrder: (value: any[], condition: OrderOptions | OrderDirection): boolean => {
-    if (typeof value === 'undefined') return true;
-    return isOrdered(value, condition);
+  sortOrder: (value: any[], condition: OrderOptions | OrderDirection, baseLocation: Location) => {
+    if (typeof value === 'undefined') return { isValid: true };
+    return { isValid: isOrdered(value, condition), location: baseLocation };
   },
-  mutuallyExclusive: (value: string[], condition: string[]): boolean => {
-    return getIntersectionLength(value, condition) < 2;
+  mutuallyExclusive: (value: string[], condition: string[], baseLocation: Location) => {
+    return { isValid: getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
   },
-  mutuallyRequired: (value: string[], condition: string[]): boolean => {
-    return getIntersectionLength(value, condition) > 0
-      ? getIntersectionLength(value, condition) === condition.length
-      : true;
+  mutuallyRequired: (value: string[], condition: string[], baseLocation: Location) => {
+    return {
+      isValid:
+        getIntersectionLength(value, condition) > 0
+          ? getIntersectionLength(value, condition) === condition.length
+          : true,
+      location: baseLocation.key(),
+    };
   },
-  requireAny: (value: string[], condition: string[]): boolean => {
-    return getIntersectionLength(value, condition) >= 1;
+  requireAny: (value: string[], condition: string[], baseLocation: Location) => {
+    return { isValid: getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
+  },
+  ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
+    if (typeof rawValue === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
+    const hasRef = rawValue.hasOwnProperty('$ref');
+    if (typeof condition === 'boolean') {
+      return {
+        isValid: condition ? hasRef : !hasRef,
+        location: hasRef ? baseLocation : baseLocation.key(),
+      };
+    }
+    const regex = regexFromString(condition);
+    return {
+      isValid: hasRef && regex?.test(rawValue['$ref']),
+      location: hasRef ? baseLocation : baseLocation.key(),
+    };
   },
 };
diff --git a/packages/core/src/rules/common/assertions/utils.ts b/packages/core/src/rules/common/assertions/utils.ts
index 4c9ff455..03e89c49 100644
--- a/packages/core/src/rules/common/assertions/utils.ts
+++ b/packages/core/src/rules/common/assertions/utils.ts
@@ -1,4 +1,4 @@
-import { isRef } from '../../../ref-utils';
+import { isRef, Location } from '../../../ref-utils';
 import { Problem, ProblemSeverity, UserContext } from '../../../walk';
 import { asserts } from './asserts';
 
@@ -23,7 +23,7 @@ export type AssertToApply = {
 export function buildVisitorObject(
   subject: string,
   context: Record<string, any>[],
-  subjectVisitor: any,
+  subjectVisitor: any
 ) {
   if (!context) {
     return { [subject]: subjectVisitor };
@@ -46,7 +46,7 @@ export function buildVisitorObject(
 
     if (matchParentKeys && excludeParentKeys) {
       throw new Error(
-        `Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`,
+        `Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`
       );
     }
 
@@ -75,9 +75,12 @@ export function buildVisitorObject(
 export function buildSubjectVisitor(
   properties: string | string[],
   asserts: AssertToApply[],
-  context?: Record<string, any>[],
+  context?: Record<string, any>[]
 ) {
-  return function (node: any, { report, location, key, type, resolve }: UserContext) {
+  return (
+    node: any,
+    { report, location, rawLocation, key, type, resolve, rawNode }: UserContext
+  ) => {
     // We need to check context's last node if it has the same type as subject node;
     // if yes - that means we didn't create context's last node visitor,
     // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
@@ -101,14 +104,28 @@ export function buildSubjectVisitor(
     }
 
     for (const assert of asserts) {
+      const currentLocation = assert.name === 'ref' ? rawLocation : location;
       if (properties) {
         for (const property of properties) {
           // we can have resolvable scalar so need to resolve value here.
           const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
-          runAssertion(value, assert, location.child(property), report);
+          runAssertion({
+            values: value,
+            rawValues: rawNode[property],
+            assert,
+            location: currentLocation.child(property),
+            report,
+          });
         }
       } else {
-        runAssertion(Object.keys(node), assert, location.key(), report);
+        const value = assert.name === 'ref' ? rawNode : Object.keys(node);
+        runAssertion({
+          values: Object.keys(node),
+          rawValues: value,
+          assert,
+          location: currentLocation,
+          report,
+        });
       }
     }
   };
@@ -148,20 +165,28 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
   return true;
 }
 
-function runAssertion(
-  values: string | string[],
-  assert: AssertToApply,
-  location: any,
-  report: (problem: Problem) => void,
-) {
-  const lintResult = asserts[assert.name](values, assert.conditions);
-  if (!lintResult) {
+type RunAssertionParams = {
+  values: string | string[];
+  rawValues: any;
+  assert: AssertToApply;
+  location: Location;
+  report: (problem: Problem) => void;
+};
+
+function runAssertion({ values, rawValues, assert, location, report }: RunAssertionParams) {
+  const lintResult = asserts[assert.name](values, assert.conditions, location, rawValues);
+  if (!lintResult.isValid) {
     report({
       message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
-      location,
+      location: lintResult.location || location,
       forceSeverity: assert.severity,
       suggest: assert.suggest,
       ruleId: assert.assertId,
     });
   }
 }
+
+export function regexFromString(input: string): RegExp | null {
+  const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
+  return matches && new RegExp(matches[1] || matches[3], matches[2]);
+}
diff --git a/packages/core/src/walk.ts b/packages/core/src/walk.ts
index f4b48efe..ca204929 100644
--- a/packages/core/src/walk.ts
+++ b/packages/core/src/walk.ts
@@ -7,7 +7,14 @@ import {
   VisitFunction,
 } from './visitors';
 
-import { ResolvedRefMap, Document, ResolveError, YamlParseError, Source, makeRefId } from './resolve';
+import {
+  ResolvedRefMap,
+  Document,
+  ResolveError,
+  YamlParseError,
+  Source,
+  makeRefId,
+} from './resolve';
 import { pushStack, popStack } from './utils';
 import { OasVersion } from './oas-types';
 import { NormalizedNodeType, isNamedType } from './types';
@@ -19,14 +26,16 @@ export type ResolveResult<T extends NonUndefined> =
 
 export type ResolveFn<T> = (
   node: Referenced<T>,
-  from?: string,
+  from?: string
 ) => { location: Location; node: T } | { location: undefined; node: undefined };
 
 export type UserContext = {
   report(problem: Problem): void;
   location: Location;
+  rawNode: any;
+  rawLocation: Location;
   resolve<T>(
-    node: Referenced<T>,
+    node: Referenced<T>
   ): { location: Location; node: T } | { location: undefined; node: undefined };
   parentLocations: Record<string, Location>;
   type: NormalizedNodeType;
@@ -121,8 +130,9 @@ export function walkDocument<T>(opts: {
     type: NormalizedNodeType,
     location: Location,
     parent: any,
-    key: string | number,
+    key: string | number
   ) {
+    const rawLocation = location;
     let currentLocation = location;
     const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
     const enteredContexts: Set<VisitorLevelContext> = new Set();
@@ -138,15 +148,17 @@ export function walkDocument<T>(opts: {
             {
               report,
               resolve,
+              rawNode: node,
+              rawLocation,
               location,
               type,
               parent,
               key,
               parentLocations: {},
               oasVersion: ctx.oasVersion,
-              getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
+              getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
             },
-            { node: resolvedNode, location: resolvedLocation, error },
+            { node: resolvedNode, location: resolvedLocation, error }
           );
           if (resolvedLocation?.source.absoluteRef && ctx.refTypes) {
             ctx.refTypes.set(resolvedLocation?.source.absoluteRef, type);
@@ -162,7 +174,7 @@ export function walkDocument<T>(opts: {
 
       const anyEnterVisitors = normalizedVisitors.any.enter;
       const currentEnterVisitors = anyEnterVisitors.concat(
-        normalizedVisitors[type.name]?.enter || [],
+        normalizedVisitors[type.name]?.enter || []
       );
 
       const activatedContexts: Array<VisitorSkippedLevelContext | VisitorLevelContext> = [];
@@ -205,7 +217,7 @@ export function walkDocument<T>(opts: {
             while (ctx) {
               ctx.activatedOn!.value.nextLevelTypeActivated = pushStack(
                 ctx.activatedOn!.value.nextLevelTypeActivated,
-                type,
+                type
               );
               ctx = ctx.parent;
             }
@@ -216,9 +228,10 @@ export function walkDocument<T>(opts: {
               const { ignoreNextVisitorsOnNode } = visitWithContext(
                 visit,
                 resolvedNode,
+                node,
                 context,
                 ruleId,
-                severity,
+                severity
               );
               if (ignoreNextVisitorsOnNode) break;
             }
@@ -282,7 +295,7 @@ export function walkDocument<T>(opts: {
 
       const anyLeaveVisitors = normalizedVisitors.any.leave;
       const currentLeaveVisitors = (normalizedVisitors[type.name]?.leave || []).concat(
-        anyLeaveVisitors,
+        anyLeaveVisitors
       );
 
       for (const context of activatedContexts.reverse()) {
@@ -294,7 +307,7 @@ export function walkDocument<T>(opts: {
             let ctx: VisitorLevelContext | null = context.parent;
             while (ctx) {
               ctx.activatedOn!.value.nextLevelTypeActivated = popStack(
-                ctx.activatedOn!.value.nextLevelTypeActivated,
+                ctx.activatedOn!.value.nextLevelTypeActivated
               );
               ctx = ctx.parent;
             }
@@ -304,7 +317,7 @@ export function walkDocument<T>(opts: {
 
       for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
         if (!context.isSkippedLevel && enteredContexts.has(context)) {
-          visitWithContext(visit, resolvedNode, context, ruleId, severity);
+          visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
         }
       }
     }
@@ -321,15 +334,17 @@ export function walkDocument<T>(opts: {
             {
               report,
               resolve,
+              rawNode: node,
+              rawLocation,
               location,
               type,
               parent,
               key,
               parentLocations: {},
               oasVersion: ctx.oasVersion,
-              getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
+              getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
             },
-            { node: resolvedNode, location: resolvedLocation, error },
+            { node: resolvedNode, location: resolvedLocation, error }
           );
         }
       }
@@ -338,37 +353,42 @@ export function walkDocument<T>(opts: {
     // returns true ignores all the next visitors on the specific node
     function visitWithContext(
       visit: VisitFunction<any>,
+      resolvedNode: any,
       node: any,
       context: VisitorLevelContext,
       ruleId: string,
-      severity: ProblemSeverity,
+      severity: ProblemSeverity
     ) {
       const report = reportFn.bind(undefined, ruleId, severity);
       let ignoreNextVisitorsOnNode = false;
 
       visit(
-        node,
+        resolvedNode,
         {
           report,
           resolve,
+          rawNode: node,
           location: currentLocation,
+          rawLocation,
           type,
           parent,
           key,
           parentLocations: collectParentsLocations(context),
           oasVersion: ctx.oasVersion,
-          ignoreNextVisitorsOnNode: () => { ignoreNextVisitorsOnNode = true; },
+          ignoreNextVisitorsOnNode: () => {
+            ignoreNextVisitorsOnNode = true;
+          },
           getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
         },
         collectParents(context),
-        context,
+        context
       );
       return { ignoreNextVisitorsOnNode };
     }
 
     function resolve<T>(
       ref: Referenced<T>,
-      from: string = currentLocation.source.absoluteRef,
+      from: string = currentLocation.source.absoluteRef
     ): ResolveResult<T> {
       if (!isRef(ref)) return { location, node: ref };
       const refId = makeRefId(from, ref.$ref);


Test patch:
diff --git a/__tests__/lint/assertions-casing-camel-case-error/.redocly.yaml b/__tests__/lint/assertions-casing-camel-case-error/.redocly.yaml
index 51437c0f..0bcfea7b 100644
--- a/__tests__/lint/assertions-casing-camel-case-error/.redocly.yaml
+++ b/__tests__/lint/assertions-casing-camel-case-error/.redocly.yaml
@@ -6,8 +6,8 @@ lint:
   rules:
     assert/operation-get:
       context:
-      - type: Operation
-        matchParentKeys: [get]
+        - type: Operation
+          matchParentKeys: [get]
       subject: Operation
       property: operationId
       message: Operation id for get operation should be camelCase
@@ -17,4 +17,8 @@ lint:
       property: operationId
       message: Operation id should be camelCase
       casing: camelCase
-  extends: []
\ No newline at end of file
+    assert/camel-case-on-value:
+      subject: NamedParameters
+      casing: camelCase
+      message: Named Parameters should be camelCase
+  extends: []
diff --git a/__tests__/lint/assertions-casing-camel-case-error/openapi.yaml b/__tests__/lint/assertions-casing-camel-case-error/openapi.yaml
index 7f2017a2..f1e56a06 100644
--- a/__tests__/lint/assertions-casing-camel-case-error/openapi.yaml
+++ b/__tests__/lint/assertions-casing-camel-case-error/openapi.yaml
@@ -35,3 +35,16 @@ paths:
           description: example description
         '404':
           description: example description
+
+components:
+  parameters:
+    camelCase:
+      in: query
+      name: type
+      schema:
+        type: string
+    Wrong_casE:
+      in: query
+      name: per_page
+      schema:
+        type: integer
diff --git a/__tests__/lint/assertions-casing-camel-case-error/snapshot.js b/__tests__/lint/assertions-casing-camel-case-error/snapshot.js
index ee0ec6b5..2cfebdbf 100644
--- a/__tests__/lint/assertions-casing-camel-case-error/snapshot.js
+++ b/__tests__/lint/assertions-casing-camel-case-error/snapshot.js
@@ -45,9 +45,23 @@ Operation id should be camelCase
 Error was generated by the operation-id-camel-case assertion rule.
 
 
+[4] openapi.yaml:46:5 at #/components/parameters/Wrong_casE
+
+Named Parameters should be camelCase
+
+44 |   schema:
+45 |     type: string
+46 | Wrong_casE:
+   | ^^^^^^^^^^
+47 |   in: query
+48 |   name: per_page
+
+Error was generated by the camel-case-on-value assertion rule.
+
+
 /openapi.yaml: validated in <test>ms
 
-❌ Validation failed with 3 errors.
+❌ Validation failed with 4 errors.
 run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
 
 
diff --git a/__tests__/lint/assertions-enum-on-keys-error/.redocly.yaml b/__tests__/lint/assertions-enum-on-keys-error/.redocly.yaml
index 88d85d99..d7b85332 100644
--- a/__tests__/lint/assertions-enum-on-keys-error/.redocly.yaml
+++ b/__tests__/lint/assertions-enum-on-keys-error/.redocly.yaml
@@ -10,4 +10,4 @@ lint:
       message: Only application/json can be used
       enum:
         - application/json
-  extends: []
\ No newline at end of file
+  extends: []
diff --git a/__tests__/lint/assertions-enum-on-keys-error/snapshot.js b/__tests__/lint/assertions-enum-on-keys-error/snapshot.js
index 0c77f653..1825f071 100644
--- a/__tests__/lint/assertions-enum-on-keys-error/snapshot.js
+++ b/__tests__/lint/assertions-enum-on-keys-error/snapshot.js
@@ -3,16 +3,16 @@
 exports[`E2E lint assertions-enum-on-keys-error 1`] = `
 
 validating /openapi.yaml...
-[1] openapi.yaml:28:11 at #/paths/~1pet~1findByStatus/get/responses/200/content
+[1] openapi.yaml:34:13 at #/paths/~1pet~1findByStatus/get/responses/200/content/application~1xml
 
 Only application/json can be used
 
-26 | '200':
-27 |   description: example description
-28 |   content:
-   |   ^^^^^^^
-29 |     application/json:
-30 |       schema:
+32 |     items:
+33 |       $ref: '#/components/schemas/Dog'
+34 | application/xml:
+   | ^^^^^^^^^^^^^^^
+35 |   schema:
+36 |     type: array
 
 Error was generated by the media-type-application-json assertion rule.
 
diff --git a/__tests__/lint/assertions-error/snapshot.js b/__tests__/lint/assertions-error/snapshot.js
index b5af92f3..6461f5af 100644
--- a/__tests__/lint/assertions-error/snapshot.js
+++ b/__tests__/lint/assertions-error/snapshot.js
@@ -124,16 +124,16 @@ Operation summary must have a maximum of 2 characters
 Error was generated by the operation-summary-max-length assertion rule.
 
 
-[9] openapi.yaml:31:9 at #/paths/~1pet~1findByStatus/put/requestBody/content
+[9] openapi.yaml:32:11 at #/paths/~1pet~1findByStatus/put/requestBody/content/application~1json
 
 Only application/pdf can be used
 
-29 |   url: "https://example.com"
 30 | requestBody:
 31 |   content:
-   |   ^^^^^^^
 32 |     application/json:
+   |     ^^^^^^^^^^^^^^^^
 33 |       schema:
+34 |         type: 'object'
 
 Error was generated by the media-type-pdf assertion rule.
 
@@ -212,16 +212,16 @@ NamedExamples key must be in PascalCase
 Error was generated by the operation-id-camel-case assertion rule.
 
 
-[15] openapi.yaml:56:5 at #/paths/~1pet~1findByStatus/post
+[15] openapi.yaml:78:7 at #/paths/~1pet~1findByStatus/post/x-code-samples
 
 x-code-samples and x-internal must not be defined
 
-54 |                 type: string
-55 |                 description: hooray
-56 | post:
-   | ^^^^
-57 |   operationId: EXAMPLE
-58 |   summary: ''
+76 |           credit-file-identity-address:
+77 |             summary: Credit file with fallback
+78 | x-code-samples:
+   | ^^^^^^^^^^^^^^
+79 |   - lang: 'C#'
+80 |     source: |
 
 Error was generated by the operation-disallowed assertion rule.
 
@@ -307,16 +307,16 @@ Operation object \`summary\` must be non-empty string.
 Error was generated by the operation-summary rule.
 
 
-[21] openapi.yaml:62:11 at #/paths/~1pet~1findByStatus/post/responses/201/content
+[21] openapi.yaml:63:13 at #/paths/~1pet~1findByStatus/post/responses/201/content/application~1pdf
 
 Media type should not be pdf
 
-60 | '201':
-61 |   description: Test description
-62 |   content:
-   |   ^^^^^^^
-63 |     application/pdf:
-64 |       schema:
+61 | description: Test description
+62 | content:
+63 |   application/pdf:
+   |   ^^^^^^^^^^^^^^^
+64 |     schema:
+65 |       type: 'object'
 
 Error was generated by the operation-w-context assertion rule.
 
diff --git a/__tests__/lint/assertions-ref-forbidden-error/.redocly.yaml b/__tests__/lint/assertions-ref-forbidden-error/.redocly.yaml
new file mode 100644
index 00000000..75ab0fbb
--- /dev/null
+++ b/__tests__/lint/assertions-ref-forbidden-error/.redocly.yaml
@@ -0,0 +1,18 @@
+apis:
+  main:
+    root: ./openapi.yaml
+
+lint:
+  rules:
+    assert/ref-forbidden:
+      context:
+        - type: Response
+      subject: MediaType
+      property: schema
+      message: Response MediaType schema should NOT have a ref
+      ref: false
+    assert/ref-forbidden-no-property:
+      subject: PathItem
+      message: PathItems should NOT should have a ref
+      ref: false
+  extends: []
diff --git a/__tests__/lint/assertions-ref-forbidden-error/components/paths/Owners.yaml b/__tests__/lint/assertions-ref-forbidden-error/components/paths/Owners.yaml
new file mode 100644
index 00000000..dc0ca6e5
--- /dev/null
+++ b/__tests__/lint/assertions-ref-forbidden-error/components/paths/Owners.yaml
@@ -0,0 +1,27 @@
+get:
+  summary: List all owners
+  operationId: listOwners
+  parameters:
+    - name: limit
+      in: query
+      description: How many items to return at one time (max 100)
+      required: false
+      schema:
+        type: integer
+        format: int
+  responses:
+    404:
+      description: test
+    200:
+      description: An paged array of owners
+      headers:
+        x-next:
+          description: A link to the next page of responses
+          schema:
+            type: string
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
diff --git a/__tests__/lint/assertions-ref-forbidden-error/components/schemas/Error.yaml b/__tests__/lint/assertions-ref-forbidden-error/components/schemas/Error.yaml
new file mode 100644
index 00000000..d19d5ca8
--- /dev/null
+++ b/__tests__/lint/assertions-ref-forbidden-error/components/schemas/Error.yaml
@@ -0,0 +1,9 @@
+required:
+  - code
+  - message
+properties:
+  code:
+    type: integer
+    format: int32
+  message:
+    type: string
diff --git a/__tests__/lint/assertions-ref-forbidden-error/openapi.yaml b/__tests__/lint/assertions-ref-forbidden-error/openapi.yaml
new file mode 100644
index 00000000..1cc266db
--- /dev/null
+++ b/__tests__/lint/assertions-ref-forbidden-error/openapi.yaml
@@ -0,0 +1,67 @@
+openapi: '3.0.0'
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  description: Information about Petstore
+  license:
+    name: MIT
+    url: https://opensource.org/licenses/MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int
+      responses:
+        404:
+          description: test
+        200:
+          description: An paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: ./components/schemas/Error.yaml
+  /owners:
+    $ref: components/paths/Owners.yaml
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'
diff --git a/__tests__/lint/assertions-ref-forbidden-error/snapshot.js b/__tests__/lint/assertions-ref-forbidden-error/snapshot.js
new file mode 100644
index 00000000..93a51ba3
--- /dev/null
+++ b/__tests__/lint/assertions-ref-forbidden-error/snapshot.js
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E lint assertions-ref-forbidden-error 1`] = `
+
+validating /openapi.yaml...
+[1] openapi.yaml:47:17 at #/paths/~1pets/get/responses/default/content/application~1json/schema
+
+Response MediaType schema should NOT have a ref
+
+45 |           application/json:
+46 |             schema:
+47 |               $ref: ./components/schemas/Error.yaml
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+48 | /owners:
+49 |   $ref: components/paths/Owners.yaml
+
+Error was generated by the ref-forbidden assertion rule.
+
+
+[2] openapi.yaml:49:5 at #/paths/~1owners
+
+PathItems should NOT should have a ref
+
+47 |                 $ref: ./components/schemas/Error.yaml
+48 |   /owners:
+49 |     $ref: components/paths/Owners.yaml
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+50 | components:
+51 |   schemas:
+
+Error was generated by the ref-forbidden-no-property assertion rule.
+
+
+/openapi.yaml: validated in <test>ms
+
+❌ Validation failed with 2 errors.
+run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
+
+
+`;
diff --git a/__tests__/lint/assertions-ref-pattern-error/.redocly.yaml b/__tests__/lint/assertions-ref-pattern-error/.redocly.yaml
new file mode 100644
index 00000000..b5f5b86f
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/.redocly.yaml
@@ -0,0 +1,18 @@
+apis:
+  main:
+    root: ./openapi.yaml
+
+lint:
+  rules:
+    assert/ref-pattern:
+      context:
+        - type: Response
+      subject: MediaType
+      property: schema
+      message: Response MediaType schema should contain ref to components/schemas folder
+      ref: ^(\.\/)?components\/schemas\/.*\.yaml$
+    assert/ref-pattern-no-properties:
+      subject: PathItem
+      message: PathItem should contain ref to components/paths folder
+      ref: ^(\.\/)?components\/paths\/.*\.yaml$
+  extends: []
diff --git a/__tests__/lint/assertions-ref-pattern-error/components/Vets.yaml b/__tests__/lint/assertions-ref-pattern-error/components/Vets.yaml
new file mode 100644
index 00000000..319afff1
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/components/Vets.yaml
@@ -0,0 +1,26 @@
+get:
+  summary: List all Vets
+  parameters:
+    - name: limit
+      in: query
+      description: How many items to return at one time (max 100)
+      required: false
+      schema:
+        type: integer
+        format: int
+  responses:
+    404:
+      description: test
+    200:
+      description: An paged array of Vets
+      headers:
+        x-next:
+          description: A link to the next page of responses
+          schema:
+            type: string
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
diff --git a/__tests__/lint/assertions-ref-pattern-error/components/paths/Owners.yaml b/__tests__/lint/assertions-ref-pattern-error/components/paths/Owners.yaml
new file mode 100644
index 00000000..dc0ca6e5
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/components/paths/Owners.yaml
@@ -0,0 +1,27 @@
+get:
+  summary: List all owners
+  operationId: listOwners
+  parameters:
+    - name: limit
+      in: query
+      description: How many items to return at one time (max 100)
+      required: false
+      schema:
+        type: integer
+        format: int
+  responses:
+    404:
+      description: test
+    200:
+      description: An paged array of owners
+      headers:
+        x-next:
+          description: A link to the next page of responses
+          schema:
+            type: string
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
diff --git a/__tests__/lint/assertions-ref-pattern-error/components/schemas/Error.yaml b/__tests__/lint/assertions-ref-pattern-error/components/schemas/Error.yaml
new file mode 100644
index 00000000..d19d5ca8
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/components/schemas/Error.yaml
@@ -0,0 +1,9 @@
+required:
+  - code
+  - message
+properties:
+  code:
+    type: integer
+    format: int32
+  message:
+    type: string
diff --git a/__tests__/lint/assertions-ref-pattern-error/openapi.yaml b/__tests__/lint/assertions-ref-pattern-error/openapi.yaml
new file mode 100644
index 00000000..525d9c35
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/openapi.yaml
@@ -0,0 +1,67 @@
+openapi: '3.0.0'
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  description: Information about Petstore
+  license:
+    name: MIT
+    url: https://opensource.org/licenses/MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int
+      responses:
+        404:
+          description: test
+        200:
+          description: An paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pets'
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: ./components/schemas/Error.yaml
+  /owners:
+    $ref: components/paths/Owners.yaml
+  /vets:
+    $ref: components/Vets.yaml
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'
diff --git a/__tests__/lint/assertions-ref-pattern-error/snapshot.js b/__tests__/lint/assertions-ref-pattern-error/snapshot.js
new file mode 100644
index 00000000..9813bcc6
--- /dev/null
+++ b/__tests__/lint/assertions-ref-pattern-error/snapshot.js
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E lint assertions-ref-pattern-error 1`] = `
+
+validating /openapi.yaml...
+[1] openapi.yaml:12:3 at #/paths/~1pets
+
+PathItem should contain ref to components/paths folder
+
+10 |   - url: http://petstore.swagger.io/v1
+11 | paths:
+12 |   /pets:
+   |   ^^^^^
+13 |     get:
+14 |       summary: List all pets
+
+Error was generated by the ref-pattern-no-properties assertion rule.
+
+
+[2] openapi.yaml:39:17 at #/paths/~1pets/get/responses/200/content/application~1json/schema
+
+Response MediaType schema should contain ref to components/schemas folder
+
+37 |     application/json:
+38 |       schema:
+39 |         $ref: '#/components/schemas/Pets'
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+40 | default:
+41 |   description: unexpected error
+
+Error was generated by the ref-pattern assertion rule.
+
+
+[3] components/paths/Owners.yaml:24:11 at #/get/responses/200/content/application~1json/schema
+
+Response MediaType schema should contain ref to components/schemas folder
+
+22 | content:
+23 |   application/json:
+24 |     schema:
+   |     ^^^^^^
+25 |       properties:
+26 |         id:
+
+Error was generated by the ref-pattern assertion rule.
+
+
+[4] openapi.yaml:49:5 at #/paths/~1vets
+
+PathItem should contain ref to components/paths folder
+
+47 |     $ref: components/paths/Owners.yaml
+48 |   /vets:
+49 |     $ref: components/Vets.yaml
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+50 | components:
+51 |   schemas:
+
+Error was generated by the ref-pattern-no-properties assertion rule.
+
+
+[5] components/Vets.yaml:23:11 at #/get/responses/200/content/application~1json/schema
+
+Response MediaType schema should contain ref to components/schemas folder
+
+21 | content:
+22 |   application/json:
+23 |     schema:
+   |     ^^^^^^
+24 |       properties:
+25 |         id:
+
+Error was generated by the ref-pattern assertion rule.
+
+
+/openapi.yaml: validated in <test>ms
+
+❌ Validation failed with 5 errors.
+run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
+
+
+`;
diff --git a/__tests__/lint/assertions-ref-required-error/.redocly.yaml b/__tests__/lint/assertions-ref-required-error/.redocly.yaml
new file mode 100644
index 00000000..72acaa80
--- /dev/null
+++ b/__tests__/lint/assertions-ref-required-error/.redocly.yaml
@@ -0,0 +1,18 @@
+apis:
+  main:
+    root: ./openapi.yaml
+
+lint:
+  rules:
+    assert/ref-required:
+      context:
+        - type: Response
+      subject: MediaType
+      property: schema
+      message: Response MediaType schema should have a ref
+      ref: true
+    assert/ref-required-no-property:
+      subject: PathItem
+      message: PathItems should have refs
+      ref: true
+  extends: []
diff --git a/__tests__/lint/assertions-ref-required-error/components/paths/Owners.yaml b/__tests__/lint/assertions-ref-required-error/components/paths/Owners.yaml
new file mode 100644
index 00000000..dc0ca6e5
--- /dev/null
+++ b/__tests__/lint/assertions-ref-required-error/components/paths/Owners.yaml
@@ -0,0 +1,27 @@
+get:
+  summary: List all owners
+  operationId: listOwners
+  parameters:
+    - name: limit
+      in: query
+      description: How many items to return at one time (max 100)
+      required: false
+      schema:
+        type: integer
+        format: int
+  responses:
+    404:
+      description: test
+    200:
+      description: An paged array of owners
+      headers:
+        x-next:
+          description: A link to the next page of responses
+          schema:
+            type: string
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
diff --git a/__tests__/lint/assertions-ref-required-error/components/schemas/Error.yaml b/__tests__/lint/assertions-ref-required-error/components/schemas/Error.yaml
new file mode 100644
index 00000000..d19d5ca8
--- /dev/null
+++ b/__tests__/lint/assertions-ref-required-error/components/schemas/Error.yaml
@@ -0,0 +1,9 @@
+required:
+  - code
+  - message
+properties:
+  code:
+    type: integer
+    format: int32
+  message:
+    type: string
diff --git a/__tests__/lint/assertions-ref-required-error/openapi.yaml b/__tests__/lint/assertions-ref-required-error/openapi.yaml
new file mode 100644
index 00000000..1cc266db
--- /dev/null
+++ b/__tests__/lint/assertions-ref-required-error/openapi.yaml
@@ -0,0 +1,67 @@
+openapi: '3.0.0'
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  description: Information about Petstore
+  license:
+    name: MIT
+    url: https://opensource.org/licenses/MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int
+      responses:
+        404:
+          description: test
+        200:
+          description: An paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:
+              schema:
+                properties:
+                  id:
+                    type: integer
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: ./components/schemas/Error.yaml
+  /owners:
+    $ref: components/paths/Owners.yaml
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'
diff --git a/__tests__/lint/assertions-ref-required-error/snapshot.js b/__tests__/lint/assertions-ref-required-error/snapshot.js
new file mode 100644
index 00000000..c02af022
--- /dev/null
+++ b/__tests__/lint/assertions-ref-required-error/snapshot.js
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E lint assertions-ref-required-error 1`] = `
+
+validating /openapi.yaml...
+[1] openapi.yaml:12:3 at #/paths/~1pets
+
+PathItems should have refs
+
+10 |   - url: http://petstore.swagger.io/v1
+11 | paths:
+12 |   /pets:
+   |   ^^^^^
+13 |     get:
+14 |       summary: List all pets
+
+Error was generated by the ref-required-no-property assertion rule.
+
+
+[2] openapi.yaml:38:15 at #/paths/~1pets/get/responses/200/content/application~1json/schema
+
+Response MediaType schema should have a ref
+
+36 | content:
+37 |   application/json:
+38 |     schema:
+   |     ^^^^^^
+39 |       properties:
+40 |         id:
+
+Error was generated by the ref-required assertion rule.
+
+
+[3] components/paths/Owners.yaml:24:11 at #/get/responses/200/content/application~1json/schema
+
+Response MediaType schema should have a ref
+
+22 | content:
+23 |   application/json:
+24 |     schema:
+   |     ^^^^^^
+25 |       properties:
+26 |         id:
+
+Error was generated by the ref-required assertion rule.
+
+
+/openapi.yaml: validated in <test>ms
+
+❌ Validation failed with 3 errors.
+run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
+
+
+`;
diff --git a/__tests__/lint/assertions/.redocly.yaml b/__tests__/lint/assertions/.redocly.yaml
index 8130b206..c3fe690b 100644
--- a/__tests__/lint/assertions/.redocly.yaml
+++ b/__tests__/lint/assertions/.redocly.yaml
@@ -12,7 +12,7 @@ lint:
       severity: error
       minLength: 13
       pattern: /\.$/
-#property example
+    # property example
     assert/path-item-get-defined:
       subject: PathItem
       property: get
@@ -30,7 +30,7 @@ lint:
         - description
       message: Every tag must have a name and description.
       defined: true
-#context example
+    # context example
     assert/media-type-map-not-pdf:
       subject:
         - MediaTypeMap
@@ -40,9 +40,9 @@ lint:
             - put
         - type: Response
           matchParentKeys: ['201', '200']
-      disallowed: [ 'application/pdf' ]
+      disallowed: ['application/pdf']
       message: Media type should not be pdf
-#enum example
+    # enum example
     assert/media-type-map-enum:
       subject: MediaTypeMap
       message: Only application/json and application/pdf can be used
@@ -61,29 +61,29 @@ lint:
       enum:
         - My resource
         - My collection
-#pattern example
+    # pattern example
     assert/operation-summary-pattern:
       subject: Operation
       property: summary
       message: Summary should match a regex
       severity: error
       pattern: /resource/
-#casing
+    # casing
     assert/operation-id-casing:
       subject: Operation
       property: operationId
       message: NamedExamples key must be in PascalCase
       severity: error
       casing: camelCase
-#mutuallyExclusive example
+    # mutuallyExclusive example
     assert/operation-mutually-exclusive:
       subject: Operation
-      message: "Operation must not define both properties together: description and externalDocs"
+      message: 'Operation must not define both properties together: description and externalDocs'
       severity: error
       mutuallyExclusive:
         - description
         - externalDocs
-#mutuallyRequired example
+    # mutuallyRequired example
     assert/schema-properties-mutually-required:
       subject: SchemaProperties
       context:
@@ -93,27 +93,27 @@ lint:
       mutuallyRequired:
         - created_at
         - updated_at
-#mutuallyRequired example with context
+    # mutuallyRequired example with context
     assert/response-map-required-with-context:
       subject: ResponsesMap
       context:
-          - type: Operation
-            matchParentKeys:
-              - put
+        - type: Operation
+          matchParentKeys:
+            - put
       message: Must mutually define 200 and 201 responses for PUT requests.
       severity: error
       mutuallyRequired:
         - '200'
         - '201'
-#requireAny example
+    # requireAny example
     assert/operation-require-any-description-or-external:
       subject: Operation
-      message: "Operation must have one of properties defined: description or externalDocs"
+      message: 'Operation must have one of properties defined: description or externalDocs'
       severity: error
       requireAny:
         - description
         - externalDocs
-#disallowed example
+    # disallowed example
     assert/operation-disallowed:
       subject: Operation
       message: x-code-samples and x-internal must not be defined
@@ -121,14 +121,14 @@ lint:
       disallowed:
         - x-code-samples
         - x-internal
-#defined example
+    # defined example
     assert/operation-defined:
       subject: Operation
       property: x-codeSamples
       message: x-codeSamples must be defined
       severity: error
       defined: true
-# undefined example
+    # undefined example
     assert/operation-undefined:
       subject: Operation
       property: x-code-samples
@@ -137,24 +137,30 @@ lint:
         - x-codeSamples instead of x-code-samples
       severity: error
       undefined: true
-#nonEmpty example
+    # nonEmpty example
     assert/operation-non-empty:
       subject: Operation
       property: summary
       message: Operation summary should not be empty
       severity: error
       nonEmpty: true
-#minLength example
+    # minLength example
     assert/operation-min-length:
       subject: Operation
       property: summary
       message: Operation summary must have minimum of 2 chars length
       severity: error
       minLength: 2
-#maxLength example
+    # maxLength example
     assert/operation-max-length:
       subject: Operation
       property: summary
       message: Operation summary must have a maximum of 2 characters
       severity: error
       maxLength: 20
+    # ref example
+    assert/ref:
+      subject: PathItem
+      message: No refs on Path Items
+      severity: error
+      ref: false
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 9ddfdc1c..e7057ec2 100644
--- a/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts
+++ b/packages/core/src/rules/common/assertions/__tests__/asserts.test.ts
@@ -1,5 +1,9 @@
+import { Location } from '../../../../ref-utils';
+import { Source } from '../../../../resolve';
 import { asserts } from '../asserts';
 
+let baseLocation = new Location(jest.fn() as any as Source, 'pointer');
+
 describe('oas3 assertions', () => {
   describe('generic rules', () => {
     const fakeNode = {
@@ -10,232 +14,231 @@ describe('oas3 assertions', () => {
 
     describe('pattern', () => {
       it('value should match regex pattern', () => {
-        expect(asserts.pattern('test string', '/test/')).toBeTruthy();
-        expect(asserts.pattern('test string', '/test me/')).toBeFalsy();
-        expect(asserts.pattern(['test string', 'test me'], '/test/')).toBeTruthy();
-        expect(asserts.pattern(['test string', 'test me'], '/test me/')).toBeFalsy();
+        expect(asserts.pattern('test string', '/test/', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.pattern('test string', '/test me/', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.pattern(['test string', 'test me'], '/test/', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.pattern(['test string', 'test me'], '/test me/', baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
+        expect(asserts.pattern('./components/smth/test.yaml', '/^(./)?components/.*.yaml$/', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.pattern('./other.yaml', '/^(./)?components/.*.yaml$/', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+      });
+    });
+
+    describe('ref', () => {
+      it('value should have ref', () => {
+        expect(asserts.ref({ $ref: 'text' }, true, baseLocation, { $ref: 'text' })).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.ref({}, true, baseLocation, {})).toEqual({ isValid: false, location: baseLocation.key() });
+      });
+
+      it('value should not have ref', () => {
+        expect(asserts.ref({ $ref: 'text' }, false, baseLocation, { $ref: 'text' })).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.ref({}, false, baseLocation, {})).toEqual({ isValid: true, location: baseLocation.key() });
+      });
+
+      it('value should match regex pattern', () => {
+        expect(asserts.ref({ $ref: 'test string' }, '/test/', baseLocation, { $ref: 'test string' })).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.ref({ $ref: 'test string' }, '/test me/', baseLocation, { $ref: 'test string' })).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.ref({ $ref: './components/smth/test.yaml' }, '/^(./)?components/.*.yaml$/', baseLocation, { $ref: './components/smth/test.yaml' })).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.ref({ $ref: './paths/smth/test.yaml' }, '/^(./)?components/.*.yaml$/', baseLocation, { $ref: './paths/smth/test.yaml' })).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('enum', () => {
       it('value should be among predefined keys', () => {
-        expect(asserts.enum('test', ['test', 'example'])).toBeTruthy();
-        expect(asserts.enum(['test'], ['test', 'example'])).toBeTruthy();
-        expect(asserts.enum(['test', 'example'], ['test', 'example'])).toBeTruthy();
-        expect(asserts.enum(['test', 'example', 'foo'], ['test', 'example'])).toBeFalsy();
-        expect(asserts.enum('test', ['foo', 'example'])).toBeFalsy();
-        expect(asserts.enum(['test', 'foo'], ['test', 'example'])).toBeFalsy();
+        expect(asserts.enum('test', ['test', 'example'], baseLocation)).toEqual({ isValid: true });
+        expect(asserts.enum(['test'], ['test', 'example'], baseLocation)).toEqual({ isValid: true });
+        expect(asserts.enum(['test', 'example'], ['test', 'example'], baseLocation)).toEqual({ isValid: true });
+        expect(asserts.enum(['test', 'example', 'foo'], ['test', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo').key() });
+        expect(asserts.enum('test', ['foo', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.enum(['test', 'foo'], ['test', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo').key() });
       });
     });
 
     describe('defined', () => {
       it('value should be defined', () => {
-        expect(asserts.defined('test', true)).toBeTruthy();
-        expect(asserts.defined(undefined, true)).toBeFalsy();
+        expect(asserts.defined('test', true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.defined(undefined, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be undefined', () => {
-        expect(asserts.defined(undefined, false)).toBeTruthy();
-        expect(asserts.defined('test', false)).toBeFalsy();
+        expect(asserts.defined(undefined, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.defined('test', false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('undefined', () => {
       it('value should be undefined', () => {
-        expect(asserts.undefined(undefined, true)).toBeTruthy();
-        expect(asserts.undefined('test', true)).toBeFalsy();
+        expect(asserts.undefined(undefined, true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.undefined('test', true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be defined', () => {
-        expect(asserts.undefined('test', false)).toBeTruthy();
-        expect(asserts.undefined(undefined, false)).toBeFalsy();
+        expect(asserts.undefined('test', false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.undefined(undefined, false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('required', () => {
       it('values should be required', () => {
-        expect(asserts.required(['one', 'two', 'three'], ['one', 'two'])).toBeTruthy();
-        expect(asserts.required(['one', 'two'], ['one', 'two', 'three'])).toBeFalsy();
+        expect(asserts.required(['one', 'two', 'three'], ['one', 'two'], baseLocation)).toEqual({ isValid: true });
+        expect(asserts.required(['one', 'two'], ['one', 'two', 'three'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
       });
     });
 
     describe('nonEmpty', () => {
       it('value should not be empty', () => {
-        expect(asserts.nonEmpty('test', true)).toBeTruthy();
-        expect(asserts.nonEmpty('', true)).toBeFalsy();
-        expect(asserts.nonEmpty(null, true)).toBeFalsy();
-        expect(asserts.nonEmpty(undefined, true)).toBeFalsy();
+        expect(asserts.nonEmpty('test', true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.nonEmpty('', true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.nonEmpty(null, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.nonEmpty(undefined, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be empty', () => {
-        expect(asserts.nonEmpty('', false)).toBeTruthy();
-        expect(asserts.nonEmpty(null, false)).toBeTruthy();
-        expect(asserts.nonEmpty(undefined, false)).toBeTruthy();
-        expect(asserts.nonEmpty('test', false)).toBeFalsy();
+        expect(asserts.nonEmpty('', false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.nonEmpty(null, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.nonEmpty(undefined, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.nonEmpty('test', false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('minLength', () => {
       it('value should have less or equal than 5 symbols length', () => {
-        expect(asserts.minLength('test', 5)).toBeFalsy();
-        expect(asserts.minLength([1, 2, 3, 4], 5)).toBeFalsy();
-        expect(asserts.minLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
-        expect(asserts.minLength([1, 2, 3, 4, 5, 6], 5)).toBeTruthy();
-        expect(asserts.minLength('example', 5)).toBeTruthy();
-        expect(asserts.minLength([], 5)).toBeFalsy();
-        expect(asserts.minLength('', 5)).toBeFalsy();
+        expect(asserts.minLength('test', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.minLength([1, 2, 3, 4], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.minLength([1, 2, 3, 4, 5], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.minLength([1, 2, 3, 4, 5, 6], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.minLength('example', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.minLength([], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.minLength('', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('maxLength', () => {
       it('value should have more or equal than 5 symbols length', () => {
-        expect(asserts.maxLength('test', 5)).toBeTruthy();
-        expect(asserts.maxLength([1, 2, 3, 4], 5)).toBeTruthy();
-        expect(asserts.maxLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
-        expect(asserts.maxLength([1, 2, 3, 4, 5, 6], 5)).toBeFalsy();
-        expect(asserts.maxLength('example', 5)).toBeFalsy();
-        expect(asserts.maxLength([], 5)).toBeTruthy();
-        expect(asserts.maxLength('', 5)).toBeTruthy();
+        expect(asserts.maxLength('test', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.maxLength([1, 2, 3, 4], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.maxLength([1, 2, 3, 4, 5], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.maxLength([1, 2, 3, 4, 5, 6], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.maxLength('example', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.maxLength([], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.maxLength('', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
       });
     });
 
     describe('casing', () => {
       it('value should be camelCase', () => {
-        expect(asserts.casing(['testExample', 'fooBar'], 'camelCase')).toBeTruthy();
-        expect(asserts.casing(['testExample', 'FooBar'], 'camelCase')).toBeFalsy();
-        expect(asserts.casing('testExample', 'camelCase')).toBeTruthy();
-        expect(asserts.casing('TestExample', 'camelCase')).toBeFalsy();
-        expect(asserts.casing('test-example', 'camelCase')).toBeFalsy();
-        expect(asserts.casing('test_example', 'camelCase')).toBeFalsy();
+        expect(asserts.casing(['testExample', 'fooBar'], 'camelCase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['testExample', 'FooBar'], 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FooBar').key() });
+        expect(asserts.casing('testExample', 'camelCase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing('TestExample', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test_example', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be PascalCase', () => {
-        expect(asserts.casing('TestExample', 'PascalCase')).toBeTruthy();
-        expect(asserts.casing(['TestExample', 'FooBar'], 'PascalCase')).toBeTruthy();
-        expect(asserts.casing(['TestExample', 'fooBar'], 'PascalCase')).toBeFalsy();
-        expect(asserts.casing('testExample', 'PascalCase')).toBeFalsy();
-        expect(asserts.casing('test-example', 'PascalCase')).toBeFalsy();
-        expect(asserts.casing('test_example', 'PascalCase')).toBeFalsy();
+        expect(asserts.casing('TestExample', 'PascalCase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TestExample', 'FooBar'], 'PascalCase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TestExample', 'fooBar'], 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('fooBar').key() });
+        expect(asserts.casing('testExample', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test_example', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be kebab-case', () => {
-        expect(asserts.casing('test-example', 'kebab-case')).toBeTruthy();
-        expect(asserts.casing(['test-example', 'foo-bar'], 'kebab-case')).toBeTruthy();
-        expect(asserts.casing(['test-example', 'foo_bar'], 'kebab-case')).toBeFalsy();
-        expect(asserts.casing('testExample', 'kebab-case')).toBeFalsy();
-        expect(asserts.casing('TestExample', 'kebab-case')).toBeFalsy();
-        expect(asserts.casing('test_example', 'kebab-case')).toBeFalsy();
+        expect(asserts.casing('test-example', 'kebab-case', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['test-example', 'foo-bar'], 'kebab-case', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['test-example', 'foo_bar'], 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo_bar').key() });
+        expect(asserts.casing('testExample', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TestExample', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test_example', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be snake_case', () => {
-        expect(asserts.casing('test_example', 'snake_case')).toBeTruthy();
-        expect(asserts.casing(['test_example', 'foo_bar'], 'snake_case')).toBeTruthy();
-        expect(asserts.casing(['test_example', 'foo-bar'], 'snake_case')).toBeFalsy();
-        expect(asserts.casing('testExample', 'snake_case')).toBeFalsy();
-        expect(asserts.casing('TestExample', 'snake_case')).toBeFalsy();
-        expect(asserts.casing('test-example', 'snake_case')).toBeFalsy();
+        expect(asserts.casing('test_example', 'snake_case', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['test_example', 'foo_bar'], 'snake_case', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['test_example', 'foo-bar'], 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo-bar').key() });
+        expect(asserts.casing('testExample', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TestExample', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be MACRO_CASE', () => {
-        expect(asserts.casing('TEST_EXAMPLE', 'MACRO_CASE')).toBeTruthy();
-        expect(asserts.casing(['TEST_EXAMPLE', 'FOO_BAR'], 'MACRO_CASE')).toBeTruthy();
-        expect(asserts.casing(['TEST_EXAMPLE', 'FOO-BAR'], 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('TEST_EXAMPLE_', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('_TEST_EXAMPLE', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('TEST__EXAMPLE', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('TEST-EXAMPLE', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('testExample', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('TestExample', 'MACRO_CASE')).toBeFalsy();
-        expect(asserts.casing('test-example', 'MACRO_CASE')).toBeFalsy();
+        expect(asserts.casing('TEST_EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TEST_EXAMPLE', 'FOO_BAR'], 'MACRO_CASE', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TEST_EXAMPLE', 'FOO-BAR'], 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FOO-BAR').key() });
+        expect(asserts.casing('TEST_EXAMPLE_', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('_TEST_EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TEST__EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TEST-EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('testExample', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TestExample', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be COBOL-CASE', () => {
-        expect(asserts.casing('TEST-EXAMPLE', 'COBOL-CASE')).toBeTruthy();
-        expect(asserts.casing(['TEST-EXAMPLE', 'FOO-BAR'], 'COBOL-CASE')).toBeTruthy();
-        expect(asserts.casing(['TEST-EXAMPLE', 'FOO_BAR'], 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('TEST-EXAMPLE-', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('0TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('-TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('TEST--EXAMPLE', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('TEST_EXAMPLE', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('testExample', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('TestExample', 'COBOL-CASE')).toBeFalsy();
-        expect(asserts.casing('test-example', 'COBOL-CASE')).toBeFalsy();
+        expect(asserts.casing('TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TEST-EXAMPLE', 'FOO-BAR'], 'COBOL-CASE', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['TEST-EXAMPLE', 'FOO_BAR'], 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FOO_BAR').key() });
+        expect(asserts.casing('TEST-EXAMPLE-', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('0TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('-TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TEST--EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TEST_EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('testExample', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TestExample', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be flatcase', () => {
-        expect(asserts.casing('testexample', 'flatcase')).toBeTruthy();
-        expect(asserts.casing(['testexample', 'foobar'], 'flatcase')).toBeTruthy();
-        expect(asserts.casing(['testexample', 'foo_bar'], 'flatcase')).toBeFalsy();
-        expect(asserts.casing('testexample_', 'flatcase')).toBeFalsy();
-        expect(asserts.casing('0testexample', 'flatcase')).toBeFalsy();
-        expect(asserts.casing('testExample', 'flatcase')).toBeFalsy();
-        expect(asserts.casing('TestExample', 'flatcase')).toBeFalsy();
-        expect(asserts.casing('test-example', 'flatcase')).toBeFalsy();
+        expect(asserts.casing('testexample', 'flatcase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['testexample', 'foobar'], 'flatcase', baseLocation)).toEqual({ isValid: true });
+        expect(asserts.casing(['testexample', 'foo_bar'], 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo_bar').key() });
+        expect(asserts.casing('testexample_', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('0testexample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('testExample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('TestExample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.casing('test-example', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe.skip('sortOrder', () => {
       it('value should be ordered in ASC direction', () => {
-        expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc')).toBeTruthy();
-        expect(asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' })).toBeTruthy();
-        expect(asserts.sortOrder(['example'], 'asc')).toBeTruthy();
-        expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc')).toBeFalsy();
-        expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc')).toBeFalsy();
-        expect(
-          asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
-            direction: 'asc',
-            property: 'name',
-          }),
-        ).toBeTruthy();
-        expect(
-          asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
-            direction: 'desc',
-            property: 'name',
-          }),
-        ).toBeFalsy();
+        expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['example'], 'asc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], { direction: 'asc', property: 'name' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], { direction: 'desc', property: 'name' }, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
       it('value should be ordered in DESC direction', () => {
-        expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc')).toBeTruthy();
-        expect(asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' })).toBeTruthy();
-        expect(asserts.sortOrder(['example'], 'desc')).toBeTruthy();
-        expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc')).toBeFalsy();
-        expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc')).toBeFalsy();
-        expect(
-          asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
-            direction: 'desc',
-            property: 'name',
-          }),
-        ).toBeTruthy();
-        expect(
-          asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
-            direction: 'asc',
-            property: 'name',
-          }),
-        ).toBeFalsy();
+        expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['example'], 'desc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
+        expect(asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], { direction: 'desc', property: 'name' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
+        expect(asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], { direction: 'asc', property: 'name' }, baseLocation)).toEqual({ isValid: false, location: baseLocation });
       });
     });
 
     describe('mutuallyExclusive', () => {
       it('node should not have more than one property from predefined list', () => {
-        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'test'])).toBeTruthy();
-        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), [])).toBeTruthy();
-        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar'])).toBeFalsy();
-        expect(
-          asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar', 'test']),
-        ).toBeFalsy();
+        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
+        expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
       });
     });
 
     describe('mutuallyRequired', () => {
       it('node should have all the properties from predefined list', () => {
-        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar'])).toBeTruthy();
-        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'baz'])).toBeTruthy();
-        expect(asserts.mutuallyRequired(Object.keys(fakeNode), [])).toBeTruthy();
-        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'test'])).toBeFalsy();
-        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'test'])).toBeFalsy();
+        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'baz'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.mutuallyRequired(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
+        expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
       });
     });
 
     describe('requireAny', () => {
       it('node must have at least one property from predefined list', () => {
-        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'test'])).toBeTruthy();
-        expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'bar'])).toBeTruthy();
-        expect(asserts.requireAny(Object.keys(fakeNode), [])).toBeFalsy();
-        expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'test1'])).toBeFalsy();
-        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar'])).toBeTruthy();
-        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar', 'test'])).toBeTruthy();
+        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.requireAny(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
+        expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'test1'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
+        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
+        expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
       });
     });
   });