Repository: Redocly/redocly-cli
Pull request #1520 (2024-04-10 09:58:15)

feat: change config validation order
## What/Why/How?

- Changes to config validation flow, linting configuration with resolved config
- Extended types mixed into other oas types


Changes in this PR also results in code changes for Project

Affected changes & tests => https://github.com/Redocly/redocly/pull/7855

<img width="306" alt="Screenshot 2024-04-15 at 14 44 19" src="https://github.com/Redocly/redocly-cli/assets/2563120/ab6cb970-c30e-46e8-9211-3738b4ab6831">

## Reference
Closes https://github.com/Redocly/redocly-cli/issues/1223

## Testing

## Screenshots (optional)

## Check yourself

- [x] Code changed? - Tested with redoc/reference-docs/workflows (internal)
- [x] All new/updated code is covered with tests
- [ ] New package installed? - Tested in different environments (browser/node)

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: f1511d41c42d6ac49f0ea0dac654c1353aa2ae88

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/Redocly/redocly-cli/new/fix/config-validation-resolve-types?filename=.changeset/mean-zoos-rhyme.md&value=---%0A%22%40redocly%2Fcli%22%3A%20patch%0A%22%40redocly%2Fopenapi-core%22%3A%20patch%0A---%0A%0Achore%3A%20wip%20config%20validation%2C%20extend%20types%20before%0A)


github-actions[bot]: | Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `redocly lint packages/core/src/benchmark/benches/rebilly.yaml` | 983.6 ± 24.0 | 953.9 | 1019.2 | 2.41 ± 0.17 |
| `redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml` | 408.9 ± 26.6 | 391.1 | 475.8 | 1.00 |

<!-- thollander/actions-comment-pull-request "latest-vs-next-comparison" -->
github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 

❌ An unexpected error occurred. For more details, [check console](https://github.com/Redocly/redocly-cli/actions/runs/8629378520) 
```
Error: The process '/opt/hostedtoolcache/node/20.12.1/x64/bin/npm' failed with exit code 1
```

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 76.66%     |    4409/5751    |
|                                                                           🟡                                                                           | Branches   | 66.71%     |    2387/3578    |
|                                                                           🟡                                                                           | Functions  | 70.54%     |     735/1042    |
|                                                                           🟡                                                                           | Lines      | 76.86%     |    4141/5388    |


<details><summary>Show files with reduced coverage 🔻</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                                      | Statements                                                      | Branches                                                        | Functions                                               | Lines                                                          |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------ | :-------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------------------------ | :------------------------------------------------------------- |
|                                                                           🟢                                                                           | <div title="core/src/types/redocly-yaml.ts">`...` / redocly-yaml.ts</div> | 90.15%                                                          | 64.29%                                                          | 100%                                                    | 89.76%                                                         |
|                                                                           🟢                                                                           | <div title="core/src/config/utils.ts">`...` / utils.ts</div>              | 97.5%                                                           | <div title="Base coverage is: 90.51%">88.61% (-1.9% 🔻)</div>   | 100%                                                    | 97.35%                                                         |
|                                                                           🟡                                                                           | <div title="core/src/config/load.ts">`...` / load.ts</div>                | <div title="Base coverage is: 88.89%">72.73% (-16.16% 🔻)</div> | <div title="Base coverage is: 80.49%">61.36% (-19.12% 🔻)</div> | 100%                                                    | <div title="Base coverage is: 90%">76.47% (-13.53% 🔻)</div>   |
|                                                                           🟢                                                                           | core/src/lint.ts                                                          | <div title="Base coverage is: 95.74%">88.89% (-6.86% 🔻)</div>  | <div title="Base coverage is: 83.33%">72.22% (-11.11% 🔻)</div> | <div title="Base coverage is: 100%">80% (-20% 🔻)</div> | <div title="Base coverage is: 95.65%">88.64% (-7.02% 🔻)</div> |

</details>

## Test suite run failed

<details><summary>Failed tests: 5/723. Failed suites: 2/102.</summary>
<br/>

```
  ● lint › lintFromString should work

    Reference to a config is required.

      44 | }> {
      45 |   if (!configPath) {
    > 46 |     throw new Error('Reference to a config is required.\n');
         |           ^
      47 |   }
      48 |
      49 |   const document = await externalRefResolver.resolveDocument(base, configPath, true);

      at packages/core/src/config/config-resolvers.ts:46:11
      at packages/core/src/config/config-resolvers.ts:8719:39
      at Object.<anonymous>.__awaiter (packages/core/src/config/config-resolvers.ts:8668:10)
      at resolveConfigFileAndRefs (packages/core/src/config/config-resolvers.ts:8855:10)
      at packages/core/src/config/load.ts:98:70
      at packages/core/src/config/load.ts:2450:40
      at Object.<anonymous>.__awaiter (packages/core/src/config/load.ts:2399:10)
      at loadConfig (packages/core/src/config/load.ts:2640:10)
      at packages/core/src/__tests__/lint.test.ts:296:31
      at packages/core/src/__tests__/lint.test.ts:8:71
      at Object.<anonymous>.__awaiter (packages/core/src/__tests__/lint.test.ts:4:12)
      at Object.<anonymous> (packages/core/src/__tests__/lint.test.ts:281:47)

  ● lint › lintConfig should alternate its behavior when supplied externalConfigTypes

    TypeError: nodeNames is not iterable

      1083 |         ...AssertionDefinitionSubject.properties,
      1084 |         type: {
    > 1085 |           enum: [...new Set(['any', ...nodeNames, 'SpecExtension'])],
           |                                        ^
      1086 |         },
      1087 |       },
      1088 |     },

      at nodeNames (packages/core/src/types/redocly-yaml.ts:1085:40)
      at packages/core/src/__tests__/lint.test.ts:1191:45
      at packages/core/src/__tests__/lint.test.ts:8:71
      at Object.<anonymous>.__awaiter (packages/core/src/__tests__/lint.test.ts:4:12)
      at Object.<anonymous> (packages/core/src/__tests__/lint.test.ts:1187:95)

```
---
```
  ● loadConfig › should resolve config http header by US region

    Reference to a config is required.

      44 | }> {
      45 |   if (!configPath) {
    > 46 |     throw new Error('Reference to a config is required.\n');
         |           ^
      47 |   }
      48 |
      49 |   const document = await externalRefResolver.resolveDocument(base, configPath, true);

      at packages/core/src/config/config-resolvers.ts:46:11
      at packages/core/src/config/config-resolvers.ts:8719:39
      at Object.<anonymous>.__awaiter (packages/core/src/config/config-resolvers.ts:8668:10)
      at resolveConfigFileAndRefs (packages/core/src/config/config-resolvers.ts:8855:10)
      at packages/core/src/config/load.ts:98:70
      at packages/core/src/config/load.ts:2450:40
      at Object.<anonymous>.__awaiter (packages/core/src/config/load.ts:2399:10)
      at loadConfig (packages/core/src/config/load.ts:2640:10)
      at packages/core/src/config/__tests__/load.test.ts:19:36
      at packages/core/src/config/__tests__/load.test.ts:8:71
      at Object.<anonymous>.__awaiter (packages/core/src/config/__tests__/load.test.ts:4:12)
      at Object.<anonymous> (packages/core/src/config/__tests__/load.test.ts:13:67)

  ● loadConfig › should resolve config http header by EU region

    Reference to a config is required.

      44 | }> {
      45 |   if (!configPath) {
    > 46 |     throw new Error('Reference to a config is required.\n');
         |           ^
      47 |   }
      48 |
      49 |   const document = await externalRefResolver.resolveDocument(base, configPath, true);

      at packages/core/src/config/config-resolvers.ts:46:11
      at packages/core/src/config/config-resolvers.ts:8719:39
      at Object.<anonymous>.__awaiter (packages/core/src/config/config-resolvers.ts:8668:10)
      at resolveConfigFileAndRefs (packages/core/src/config/config-resolvers.ts:8855:10)
      at packages/core/src/config/load.ts:98:70
      at packages/core/src/config/load.ts:2450:40
      at Object.<anonymous>.__awaiter (packages/core/src/config/load.ts:2399:10)
      at loadConfig (packages/core/src/config/load.ts:2640:10)
      at packages/core/src/config/__tests__/load.test.ts:42:36
      at packages/core/src/config/__tests__/load.test.ts:8:71
      at Object.<anonymous>.__awaiter (packages/core/src/config/__tests__/load.test.ts:4:12)
      at Object.<anonymous> (packages/core/src/config/__tests__/load.test.ts:36:67)

  ● getConfig › should resolve refs in config

    TypeError: Cannot read properties of undefined (reading 'map')

      16 | export function replaceSourceWithRef(results: NormalizedProblem[], cwd?: string) {
      17 |   const cwdRegexp = cwd ? new RegExp(cwd + path.sep, 'g') : /$^/;
    > 18 |   return results.map((r) => {
         |                  ^
      19 |     const mapped = {
      20 |       ...r,
      21 |       message: r.message.replace(cwdRegexp, ''),

      at replaceSourceWithRef (packages/core/__tests__/utils.ts:18:18)
      at packages/core/src/config/__tests__/load.test.ts:121:32
      at fulfilled (packages/core/src/config/__tests__/load.test.ts:5:58)

```

</details>


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


Patch:
diff --git a/.changeset/tender-vans-cover.md b/.changeset/tender-vans-cover.md
new file mode 100644
index 00000000..0babfee0
--- /dev/null
+++ b/.changeset/tender-vans-cover.md
@@ -0,0 +1,6 @@
+---
+"@redocly/openapi-core": minor
+"@redocly/cli": minor
+---
+
+Changed resolution process to include extendedTypes and plugins before linting.
diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts
index 3eb41b85..d7df1beb 100644
--- a/packages/cli/src/commands/lint.ts
+++ b/packages/cli/src/commands/lint.ts
@@ -21,8 +21,8 @@ import {
 import { blue, gray } from 'colorette';
 import { performance } from 'perf_hooks';
 
-import type { OutputFormat, ProblemSeverity, Document, RuleSeverity } from '@redocly/openapi-core';
-import type { ResolvedRefMap } from '@redocly/openapi-core/lib/resolve';
+import type { OutputFormat, ProblemSeverity, RuleSeverity } from '@redocly/openapi-core';
+import type { RawConfigProcessor } from '@redocly/openapi-core/lib/config';
 import type { CommandOptions, Skips, Totals } from '../types';
 import { getCommandNameFromArgs } from '../utils/getCommandNameFromArgs';
 import { Arguments } from 'yargs';
@@ -120,7 +120,7 @@ export async function handleLint(argv: LintOptions, config: Config, version: str
 export function lintConfigCallback(
   argv: CommandOptions & Record<string, undefined>,
   version: string
-) {
+): RawConfigProcessor | undefined {
   if (argv['lint-config'] === 'off') {
     return;
   }
@@ -130,10 +130,11 @@ export function lintConfigCallback(
     return;
   }
 
-  return async (document: Document, resolvedRefMap: ResolvedRefMap) => {
+  return async ({ document, resolvedRefMap, config }) => {
     const problems = await lintConfig({
       document,
       resolvedRefMap,
+      config,
       severity: (argv['lint-config'] || 'warn') as ProblemSeverity,
     });
 
diff --git a/packages/core/src/config/load.ts b/packages/core/src/config/load.ts
index 56082c44..c5e2d4d3 100644
--- a/packages/core/src/config/load.ts
+++ b/packages/core/src/config/load.ts
@@ -4,7 +4,7 @@ import { RedoclyClient } from '../redocly';
 import { isEmptyObject } from '../utils';
 import { parseYaml } from '../js-yaml';
 import { Config } from './config';
-import { ConfigValidationError, transformConfig } from './utils';
+import { ConfigValidationError, transformConfig, deepCloneMapWithJSON } from './utils';
 import { resolveConfig, resolveConfigFileAndRefs } from './config-resolvers';
 import { bundleConfig } from '../bundle';
 import { BaseResolver } from '../resolve';
@@ -80,10 +80,12 @@ async function addConfigMetadata({
   });
 }
 
-export type RawConfigProcessor = (
-  rawConfig: Document,
-  resolvedRefMap: ResolvedRefMap
-) => void | Promise<void>;
+export type RawConfigProcessor = (params: {
+  document: Document;
+  resolvedRefMap: ResolvedRefMap;
+  config: Config;
+  parsed: Document['parsed'];
+}) => void | Promise<void>;
 
 export async function loadConfig(
   options: {
@@ -103,12 +105,16 @@ export async function loadConfig(
     region,
     externalRefResolver,
   } = options;
-  const rawConfig = await getConfig({ configPath, processRawConfig, externalRefResolver });
+
+  const { rawConfig, document, parsed, resolvedRefMap } = await getConfig({
+    configPath,
+    externalRefResolver,
+  });
 
   const redoclyClient = isBrowser ? undefined : new RedoclyClient();
   const tokens = redoclyClient && redoclyClient.hasTokens() ? redoclyClient.getAllTokens() : [];
 
-  return addConfigMetadata({
+  const config = await addConfigMetadata({
     rawConfig,
     customExtends,
     configPath,
@@ -117,6 +123,24 @@ export async function loadConfig(
     region,
     externalRefResolver,
   });
+
+  if (document && parsed && resolvedRefMap && typeof processRawConfig === 'function') {
+    try {
+      await processRawConfig({
+        document,
+        resolvedRefMap,
+        config,
+        parsed,
+      });
+    } catch (e) {
+      if (e instanceof ConfigValidationError) {
+        throw e;
+      }
+      throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
+    }
+  }
+
+  return config;
 }
 
 export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
@@ -139,31 +163,33 @@ export function findConfig(dir?: string): string | undefined {
 export async function getConfig(
   options: {
     configPath?: string;
-    processRawConfig?: RawConfigProcessor;
     externalRefResolver?: BaseResolver;
   } = {}
-): Promise<RawConfig> {
-  const {
-    configPath = findConfig(),
-    processRawConfig,
-    externalRefResolver = new BaseResolver(),
-  } = options;
-  if (!configPath) return {};
+): Promise<{
+  rawConfig: RawConfig;
+  document?: Document;
+  parsed?: Document['parsed'];
+  resolvedRefMap?: ResolvedRefMap;
+}> {
+  const { configPath = findConfig(), externalRefResolver = new BaseResolver() } = options;
+  if (!configPath) return { rawConfig: {} };
 
   try {
     const { document, resolvedRefMap } = await resolveConfigFileAndRefs({
       configPath,
       externalRefResolver,
     });
-    if (typeof processRawConfig === 'function') {
-      await processRawConfig(document, resolvedRefMap);
-    }
-    const bundledConfig = await bundleConfig(document, resolvedRefMap);
-    return transformConfig(bundledConfig);
+
+    const bundledRefMap = deepCloneMapWithJSON(resolvedRefMap);
+    const parsed = await bundleConfig(JSON.parse(JSON.stringify(document)), bundledRefMap);
+
+    return {
+      rawConfig: transformConfig(parsed),
+      document,
+      parsed,
+      resolvedRefMap,
+    };
   } catch (e) {
-    if (e instanceof ConfigValidationError) {
-      throw e;
-    }
     throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
   }
 }
diff --git a/packages/core/src/config/utils.ts b/packages/core/src/config/utils.ts
index 137481a7..cb86144f 100644
--- a/packages/core/src/config/utils.ts
+++ b/packages/core/src/config/utils.ts
@@ -364,3 +364,7 @@ export function getUniquePlugins(plugins: Plugin[]): Plugin[] {
 }
 
 export class ConfigValidationError extends Error {}
+
+export function deepCloneMapWithJSON<K, V>(originalMap: Map<K, V>): Map<K, V> {
+  return new Map(JSON.parse(JSON.stringify([...originalMap])));
+}
diff --git a/packages/core/src/lint.ts b/packages/core/src/lint.ts
index ceff0a97..ee29ac49 100755
--- a/packages/core/src/lint.ts
+++ b/packages/core/src/lint.ts
@@ -1,11 +1,11 @@
 import { BaseResolver, resolveDocument, makeDocumentFromString } from './resolve';
 import { normalizeVisitors } from './visitors';
 import { walkDocument } from './walk';
-import { StyleguideConfig, Config, initRules, defaultPlugin, resolvePlugins } from './config';
+import { StyleguideConfig, Config, initRules } from './config';
 import { normalizeTypes } from './types';
 import { releaseAjvInstance } from './rules/ajv';
 import { SpecVersion, getMajorSpecVersion, detectSpec, getTypes } from './oas-types';
-import { ConfigTypes } from './types/redocly-yaml';
+import { createConfigTypes } from './types/redocly-yaml';
 import { Spec } from './rules/common/spec';
 import { NoUnresolvedRefs } from './rules/no-unresolved-refs';
 
@@ -13,6 +13,7 @@ import type { Document, ResolvedRefMap } from './resolve';
 import type { ProblemSeverity, WalkContext } from './walk';
 import type { NodeType } from './types';
 import type { NestedVisitObject, Oas3Visitor, RuleInstanceConfig } from './visitors';
+import { rootRedoclyConfigSchema } from '@redocly/config';
 
 export async function lint(opts: {
   ref: string;
@@ -109,25 +110,25 @@ export async function lintDocument(opts: {
 
 export async function lintConfig(opts: {
   document: Document;
+  config: Config;
   resolvedRefMap?: ResolvedRefMap;
   severity?: ProblemSeverity;
   externalRefResolver?: BaseResolver;
   externalConfigTypes?: Record<string, NodeType>;
 }) {
-  const { document, severity, externalRefResolver = new BaseResolver() } = opts;
+  const { document, severity, externalRefResolver = new BaseResolver(), config } = opts;
 
   const ctx: WalkContext = {
     problems: [],
     oasVersion: SpecVersion.OAS3_0,
     visitorsData: {},
   };
-  const plugins = resolvePlugins([defaultPlugin]);
-  const config = new StyleguideConfig({
-    plugins,
-    rules: { spec: 'error' },
-  });
 
-  const types = normalizeTypes(opts.externalConfigTypes || ConfigTypes, config);
+  const types = normalizeTypes(
+    opts.externalConfigTypes || createConfigTypes(rootRedoclyConfigSchema, config),
+    { doNotResolveExamples: config.styleguide.doNotResolveExamples }
+  );
+
   const rules: (RuleInstanceConfig & {
     visitor: NestedVisitObject<unknown, Oas3Visitor | Oas3Visitor[]>;
   })[] = [
diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts
index 7d99b150..4f4cd995 100644
--- a/packages/core/src/types/redocly-yaml.ts
+++ b/packages/core/src/types/redocly-yaml.ts
@@ -5,6 +5,8 @@ import { getNodeTypesFromJSONSchema } from './json-schema-adapter';
 
 import type { NodeType } from '.';
 import type { JSONSchema } from 'json-schema-to-ts';
+import { SpecVersion, getTypes } from '../oas-types';
+import { Config } from '../config';
 
 const builtInCommonRules = [
   'spec',
@@ -222,8 +224,6 @@ const oas3_1NodeTypesList = [
 
 export type Oas3_1NodeType = typeof oas3_1NodeTypesList[number];
 
-const asyncNodeTypesList = ['Message'] as const;
-
 const ConfigStyleguide: NodeType = {
   properties: {
     extends: {
@@ -350,35 +350,28 @@ const Schema: NodeType = {
   additionalProperties: {},
 };
 
-const AssertionDefinitionSubject: NodeType = {
-  properties: {
-    type: {
-      enum: [
-        ...new Set([
-          'any',
-          ...oas2NodeTypesList,
-          ...oas3NodeTypesList,
-          ...oas3_1NodeTypesList,
-          ...asyncNodeTypesList,
-          'SpecExtension',
-        ]),
-      ],
-    },
-    property: (value: unknown) => {
-      if (Array.isArray(value)) {
-        return { type: 'array', items: { type: 'string' } };
-      } else if (value === null) {
-        return null;
-      } else {
-        return { type: 'string' };
-      }
+function createAssertionDefinitionSubject(nodeNames: string[]): NodeType {
+  return {
+    properties: {
+      type: {
+        enum: [...new Set(['any', ...nodeNames, 'SpecExtension'])],
+      },
+      property: (value: unknown) => {
+        if (Array.isArray(value)) {
+          return { type: 'array', items: { type: 'string' } };
+        } else if (value === null) {
+          return null;
+        } else {
+          return { type: 'string' };
+        }
+      },
+      filterInParentKeys: { type: 'array', items: { type: 'string' } },
+      filterOutParentKeys: { type: 'array', items: { type: 'string' } },
+      matchParentKeys: { type: 'string' },
     },
-    filterInParentKeys: { type: 'array', items: { type: 'string' } },
-    filterOutParentKeys: { type: 'array', items: { type: 'string' } },
-    matchParentKeys: { type: 'string' },
-  },
-  required: ['type'],
-};
+    required: ['type'],
+  };
+}
 
 const AssertionDefinitionAssertions: NodeType = {
   properties: {
@@ -1057,7 +1050,13 @@ const ConfigMockServer: NodeType = {
   },
 };
 
-export const createConfigTypes = (extraSchemas: JSONSchema) => {
+export function createConfigTypes(extraSchemas: JSONSchema, config?: Config) {
+  const nodeNames = Object.values(SpecVersion).flatMap((version) => {
+    const types = config?.styleguide
+      ? config.styleguide.extendTypes(getTypes(version), version)
+      : getTypes(version);
+    return Object.keys(types);
+  });
   // Create types based on external schemas
   const nodeTypes = getNodeTypesFromJSONSchema('rootRedoclyConfigSchema', extraSchemas);
 
@@ -1065,9 +1064,10 @@ export const createConfigTypes = (extraSchemas: JSONSchema) => {
     ...CoreConfigTypes,
     ConfigRoot: createConfigRoot(nodeTypes), // This is the REAL config root type
     ConfigApisProperties: createConfigApisProperties(nodeTypes),
+    AssertionDefinitionSubject: createAssertionDefinitionSubject(nodeNames),
     ...nodeTypes,
   };
-};
+}
 
 const CoreConfigTypes: Record<string, NodeType> = {
   Assert,
@@ -1130,7 +1130,6 @@ const CoreConfigTypes: Record<string, NodeType> = {
   Heading,
   Typography,
   AssertionDefinitionAssertions,
-  AssertionDefinitionSubject,
 };
 
 export const ConfigTypes: Record<string, NodeType> = createConfigTypes(rootRedoclyConfigSchema);


Test patch:
diff --git a/__tests__/check-config/config-type-extensions-in-assertions/openapi.yaml b/__tests__/check-config/config-type-extensions-in-assertions/openapi.yaml
new file mode 100644
index 00000000..b38dfc95
--- /dev/null
+++ b/__tests__/check-config/config-type-extensions-in-assertions/openapi.yaml
@@ -0,0 +1,7 @@
+openapi: 3.1.0
+info:
+  title: Food Empire API
+  version: 0.5.1
+  x-metadata:
+    lifecycle: production
+    owner-team: Engineering/Integrations
diff --git a/__tests__/check-config/config-type-extensions-in-assertions/plugins/type-extention.js b/__tests__/check-config/config-type-extensions-in-assertions/plugins/type-extention.js
new file mode 100644
index 00000000..7dca3c6a
--- /dev/null
+++ b/__tests__/check-config/config-type-extensions-in-assertions/plugins/type-extention.js
@@ -0,0 +1,27 @@
+const XMetaData = {
+  properties: {
+    lifecycle: { type: 'string', enum: ['development', 'staging', 'production'] },
+    'owner-team': { type: 'string' },
+  },
+  required: ['lifecycle'],
+};
+
+module.exports = {
+  id: 'type-extension',
+  typeExtension: {
+    oas3(types) {
+      newTypes = {
+        ...types,
+        XMetaData: XMetaData,
+        Info: {
+          ...types.Info,
+          properties: {
+            ...types.Info.properties,
+            'x-metadata': 'XMetaData',
+          },
+        },
+      };
+      return newTypes;
+    },
+  },
+};
diff --git a/__tests__/check-config/config-type-extensions-in-assertions/redocly.yaml b/__tests__/check-config/config-type-extensions-in-assertions/redocly.yaml
new file mode 100644
index 00000000..d2bd1ac4
--- /dev/null
+++ b/__tests__/check-config/config-type-extensions-in-assertions/redocly.yaml
@@ -0,0 +1,13 @@
+extends: []
+
+plugins:
+  - plugins/type-extention.js
+
+rules:
+  spec: warn
+  rule/metadata-lifecycle:
+    subject:
+      type: XMetaData
+      property: 'lifecycle'
+    assertions:
+      enum: ['alpha', 'beta', 'production', 'deprecated']
diff --git a/__tests__/check-config/config-type-extensions-in-assertions/snapshot.js b/__tests__/check-config/config-type-extensions-in-assertions/snapshot.js
new file mode 100644
index 00000000..77d1c145
--- /dev/null
+++ b/__tests__/check-config/config-type-extensions-in-assertions/snapshot.js
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E check-config config type extension in assertions 1`] = `
+
+✅  Your config is valid.
+
+`;
diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/openapi.yaml b/__tests__/check-config/wrong-config-type-extensions-in-assertions/openapi.yaml
new file mode 100644
index 00000000..b38dfc95
--- /dev/null
+++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/openapi.yaml
@@ -0,0 +1,7 @@
+openapi: 3.1.0
+info:
+  title: Food Empire API
+  version: 0.5.1
+  x-metadata:
+    lifecycle: production
+    owner-team: Engineering/Integrations
diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/plugins/type-extention.js b/__tests__/check-config/wrong-config-type-extensions-in-assertions/plugins/type-extention.js
new file mode 100644
index 00000000..7dca3c6a
--- /dev/null
+++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/plugins/type-extention.js
@@ -0,0 +1,27 @@
+const XMetaData = {
+  properties: {
+    lifecycle: { type: 'string', enum: ['development', 'staging', 'production'] },
+    'owner-team': { type: 'string' },
+  },
+  required: ['lifecycle'],
+};
+
+module.exports = {
+  id: 'type-extension',
+  typeExtension: {
+    oas3(types) {
+      newTypes = {
+        ...types,
+        XMetaData: XMetaData,
+        Info: {
+          ...types.Info,
+          properties: {
+            ...types.Info.properties,
+            'x-metadata': 'XMetaData',
+          },
+        },
+      };
+      return newTypes;
+    },
+  },
+};
diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/redocly.yaml b/__tests__/check-config/wrong-config-type-extensions-in-assertions/redocly.yaml
new file mode 100644
index 00000000..f12a33c1
--- /dev/null
+++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/redocly.yaml
@@ -0,0 +1,13 @@
+extends: []
+
+plugins:
+  - plugins/type-extention.js
+
+rules:
+  spec: warn
+  rule/metadata-lifecycle:
+    subject:
+      type: WrongXMetaData
+      property: 'lifecycle'
+    assertions:
+      enum: ['alpha', 'beta', 'production', 'deprecated']
diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js
new file mode 100644
index 00000000..9791b003
--- /dev/null
+++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E check-config wrong config type extension in assertions 1`] = `
+
+[1] redocly.yaml:10:13 at #/rules/rule~1metadata-lifecycle/subject/type
+
+\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "XMetaData", "NamedPathItems", "ServerMap", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "NamedStreamHeaders", "SecuritySchemeFlows", "Message", "MessageBindings", "OperationBindings", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "CorrelationId", "SpecExtension".
+
+ 8 | rule/metadata-lifecycle:
+ 9 |   subject:
+10 |     type: WrongXMetaData
+   |           ^^^^^^^^^^^^^^
+11 |     property: 'lifecycle'
+12 |   assertions:
+
+Error was generated by the configuration spec rule.
+
+
+❌ Your config has 1 error.
+
+
+`;
diff --git a/__tests__/commands.test.ts b/__tests__/commands.test.ts
index af65decb..cf53ed93 100644
--- a/__tests__/commands.test.ts
+++ b/__tests__/commands.test.ts
@@ -90,6 +90,30 @@ describe('E2E', () => {
       const result = getCommandOutput(passedArgs, folderPath);
       (expect(result) as any).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
     });
+
+    test('config type extension in assertions', () => {
+      const dirName = 'config-type-extensions-in-assertions';
+      const folderPath = join(__dirname, `check-config/${dirName}`);
+
+      const passedArgs = getParams('../../../packages/cli/src/index.ts', 'check-config', [
+        '--config=redocly.yaml',
+      ]);
+
+      const result = getCommandOutput(passedArgs, folderPath);
+      (expect(result) as any).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
+    });
+
+    test('wrong config type extension in assertions', () => {
+      const dirName = 'wrong-config-type-extensions-in-assertions';
+      const folderPath = join(__dirname, `check-config/${dirName}`);
+
+      const passedArgs = getParams('../../../packages/cli/src/index.ts', 'check-config', [
+        '--config=redocly.yaml',
+      ]);
+
+      const result = getCommandOutput(passedArgs, folderPath);
+      (expect(result) as any).toMatchSpecificSnapshot(join(folderPath, 'snapshot.js'));
+    });
   });
 
   describe('lint-config', () => {
diff --git a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
index 1bf7a309..fbcfb50c 100644
--- a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
+++ b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
@@ -2,9 +2,11 @@
 
 exports[`E2E lint-config test with option: { dirName: 'invalid-config-assertation-config-type', option: 'warn' } 1`] = `
 
+
+The 'assert/' syntax in assert/path-item-mutually-required is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/
 [1] .redocly.yaml:9:17 at #/rules/assert~1path-item-mutually-required/where/0/subject/type
 
-\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "ExternalDocs", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Paths", "PathItem", "Parameter", "ParameterList", "ParameterItems", "Operation", "Example", "ExamplesMap", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "TagGroup", "TagGroups", "EnumDescriptions", "Logo", "XCodeSample", "XCodeSampleList", "XServer", "XServerList", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "LinksMap", "DiscriminatorMapping", "Discriminator", "Components", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "NamedPathItems", "Message", "SpecExtension".
+\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "NamedPathItems", "ServerMap", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "NamedStreamHeaders", "SecuritySchemeFlows", "Message", "MessageBindings", "OperationBindings", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "CorrelationId", "SpecExtension".
 
  7 | where:
  8 |   - subject:
@@ -17,8 +19,6 @@ Warning was generated by the configuration spec rule.
 
 
 ⚠️ Your config has 1 warning.
-
-The 'assert/' syntax in assert/path-item-mutually-required is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/
 validating ../__fixtures__/valid-openapi.yaml...
 ../__fixtures__/valid-openapi.yaml: validated in <test>ms
 
diff --git a/__tests__/lint/deprecated-apiDefinitions/snapshot.js b/__tests__/lint/deprecated-apiDefinitions/snapshot.js
index d5505a7f..be7a7208 100644
--- a/__tests__/lint/deprecated-apiDefinitions/snapshot.js
+++ b/__tests__/lint/deprecated-apiDefinitions/snapshot.js
@@ -2,6 +2,8 @@
 
 exports[`E2E lint deprecated-apiDefinitions 1`] = `
 
+The 'apiDefinitions' field is deprecated. Use apis instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
+The 'lint' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 [1] redocly.yaml:1:1 at #/apiDefinitions
 
 Property \`apiDefinitions\` is not expected here.
@@ -31,8 +33,6 @@ Warning was generated by the configuration spec rule.
 
 
 ⚠️ Your config has 2 warnings.
-The 'apiDefinitions' field is deprecated. Use apis instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
-The 'lint' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 validating /openapi.yaml...
 [1] openapi.yaml:2:1 at #/info/contact
 
diff --git a/__tests__/lint/deprecated-lint/snapshot.js b/__tests__/lint/deprecated-lint/snapshot.js
index b04ab316..570aa4ea 100644
--- a/__tests__/lint/deprecated-lint/snapshot.js
+++ b/__tests__/lint/deprecated-lint/snapshot.js
@@ -2,6 +2,7 @@
 
 exports[`E2E lint deprecated-lint 1`] = `
 
+The 'lint' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 [1] redocly.yaml:8:1 at #/lint
 
 Property \`lint\` is not expected here.
@@ -35,7 +36,6 @@ Warning was generated by the configuration spec rule.
 
 
 ⚠️ Your config has 2 warnings.
-The 'lint' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 validating /openapi.yaml...
 [1] openapi.yaml:11:7 at #/paths/~1pet~1findByStatus/get/responses
 
diff --git a/__tests__/lint/deprecated-styleguide/snapshot.js b/__tests__/lint/deprecated-styleguide/snapshot.js
index 06227399..ae63d546 100644
--- a/__tests__/lint/deprecated-styleguide/snapshot.js
+++ b/__tests__/lint/deprecated-styleguide/snapshot.js
@@ -2,6 +2,7 @@
 
 exports[`E2E lint deprecated-styleguide 1`] = `
 
+The 'styleguide' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 [1] redocly.yaml:8:1 at #/styleguide
 
 Property \`styleguide\` is not expected here.
@@ -31,7 +32,6 @@ Warning was generated by the configuration spec rule.
 
 
 ⚠️ Your config has 2 warnings.
-The 'styleguide' field is deprecated. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties
 validating /openapi.yaml...
 [1] openapi.yaml:11:7 at #/paths/~1pet~1findByStatus/get/responses
 
diff --git a/packages/core/src/__tests__/lint.test.ts b/packages/core/src/__tests__/lint.test.ts
index a585a0d2..89c54a51 100644
--- a/packages/core/src/__tests__/lint.test.ts
+++ b/packages/core/src/__tests__/lint.test.ts
@@ -3,7 +3,7 @@ import { outdent } from 'outdent';
 
 import { lintFromString, lintConfig, lintDocument, lint } from '../lint';
 import { BaseResolver } from '../resolve';
-import { loadConfig } from '../config/load';
+import { createConfig, loadConfig } from '../config/load';
 import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../__tests__/utils';
 import { detectSpec } from '../oas-types';
 import { rootRedoclyConfigSchema } from '@redocly/config';
@@ -293,7 +293,7 @@ describe('lint', () => {
           - url: http://redocly-example.com
         paths: {}
       `,
-      config: await loadConfig(),
+      config: await loadConfig({ configPath: path.join(__dirname, 'fixtures/redocly.yaml') }),
     });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
@@ -374,7 +374,8 @@ describe('lint', () => {
       `,
       ''
     );
-    const results = await lintConfig({ document });
+    const config = await createConfig({});
+    const results = await lintConfig({ document, config });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
       [
@@ -435,7 +436,8 @@ describe('lint', () => {
       `,
       ''
     );
-    const results = await lintConfig({ document });
+    const config = await createConfig({});
+    const results = await lintConfig({ document, config });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
       [
@@ -475,7 +477,8 @@ describe('lint', () => {
       `,
       ''
     );
-    const results = await lintConfig({ document });
+    const config = await createConfig({});
+    const results = await lintConfig({ document, config });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
       [
@@ -510,7 +513,8 @@ describe('lint', () => {
       `,
       ''
     );
-    const results = await lintConfig({ document });
+    const config = await createConfig({});
+    const results = await lintConfig({ document, config });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
       [
@@ -534,7 +538,8 @@ describe('lint', () => {
 
   it('lintConfig should detect wrong fields in the default configuration after merging with the portal config schema', async () => {
     const document = testPortalConfig;
-    const results = await lintConfig({ document });
+    const config = await createConfig({});
+    const results = await lintConfig({ document, config });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
       [
@@ -1165,13 +1170,18 @@ describe('lint', () => {
 
   it('lintConfig should alternate its behavior when supplied externalConfigTypes', async () => {
     const document = testPortalConfig;
+    const config = await createConfig({});
     const results = await lintConfig({
       document,
-      externalConfigTypes: createConfigTypes({
-        type: 'object',
-        properties: { theme: rootRedoclyConfigSchema.properties.theme },
-        additionalProperties: false,
-      }),
+      externalConfigTypes: createConfigTypes(
+        {
+          type: 'object',
+          properties: { theme: rootRedoclyConfigSchema.properties.theme },
+          additionalProperties: false,
+        },
+        config
+      ),
+      config,
     });
 
     expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
diff --git a/packages/core/src/config/__tests__/load.test.ts b/packages/core/src/config/__tests__/load.test.ts
index 52cad6b8..9776aba3 100644
--- a/packages/core/src/config/__tests__/load.test.ts
+++ b/packages/core/src/config/__tests__/load.test.ts
@@ -58,6 +58,88 @@ describe('loadConfig', () => {
     expect(mockFn).toHaveBeenCalled();
   });
 
+  it('should resolve config and call processRawConfig', async () => {
+    let problems: NormalizedProblem[];
+    let doc: any;
+
+    await loadConfig({
+      configPath: path.join(__dirname, './fixtures/resolve-refs-in-config/config-with-refs.yaml'),
+      processRawConfig: async ({ document, parsed, resolvedRefMap, config }) => {
+        doc = parsed;
+        problems = await lintConfig({
+          document,
+          severity: 'warn',
+          resolvedRefMap,
+          config,
+        });
+      },
+    });
+
+    expect(replaceSourceWithRef(problems!, __dirname)).toMatchInlineSnapshot(`
+      [
+        {
+          "from": {
+            "pointer": "#/seo",
+            "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
+          },
+          "location": [
+            {
+              "pointer": "#/title",
+              "reportOnKey": false,
+              "source": "fixtures/resolve-refs-in-config/seo.yaml",
+            },
+          ],
+          "message": "Expected type \`string\` but got \`integer\`.",
+          "ruleId": "configuration spec",
+          "severity": "warn",
+          "suggest": [],
+        },
+        {
+          "from": {
+            "pointer": "#/rules",
+            "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
+          },
+          "location": [
+            {
+              "pointer": "#/non-existing-rule",
+              "reportOnKey": true,
+              "source": "fixtures/resolve-refs-in-config/rules.yaml",
+            },
+          ],
+          "message": "Property \`non-existing-rule\` is not expected here.",
+          "ruleId": "configuration spec",
+          "severity": "warn",
+          "suggest": [],
+        },
+        {
+          "location": [
+            {
+              "pointer": "#/theme",
+              "reportOnKey": false,
+              "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
+            },
+          ],
+          "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
+          "ruleId": "configuration no-unresolved-refs",
+          "severity": "warn",
+          "suggest": [],
+        },
+      ]
+    `);
+    expect(doc).toMatchInlineSnapshot(`
+      {
+        "rules": {
+          "info-license": "error",
+          "non-existing-rule": "warn",
+        },
+        "seo": {
+          "title": 1,
+        },
+        "theme": undefined,
+      }
+    `);
+  });
+
   it('should call externalRefResolver if such passed', async () => {
     const externalRefResolver = new BaseResolver();
     const resolverSpy = jest.spyOn(externalRefResolver, 'resolveDocument');
@@ -104,22 +186,16 @@ describe('findConfig', () => {
 describe('getConfig', () => {
   jest.spyOn(fs, 'hasOwnProperty').mockImplementation(() => false);
   it('should return empty object if there is no configPath and config file is not found', () => {
-    expect(getConfig()).toEqual(Promise.resolve({}));
+    expect(getConfig()).toEqual(Promise.resolve({ rawConfig: {} }));
   });
 
   it('should resolve refs in config', async () => {
     let problems: NormalizedProblem[];
-    const result = await getConfig({
+
+    const { rawConfig } = await getConfig({
       configPath: path.join(__dirname, './fixtures/resolve-refs-in-config/config-with-refs.yaml'),
-      processRawConfig: async (config, resolvedRefMap) => {
-        problems = await lintConfig({
-          document: config,
-          severity: 'warn',
-          resolvedRefMap,
-        });
-      },
     });
-    expect(result).toEqual({
+    expect(rawConfig).toEqual({
       seo: {
         title: 1,
       },
@@ -130,57 +206,6 @@ describe('getConfig', () => {
         },
       },
     });
-    expect(replaceSourceWithRef(problems!, __dirname)).toMatchInlineSnapshot(`
-      [
-        {
-          "from": {
-            "pointer": "#/seo",
-            "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
-          },
-          "location": [
-            {
-              "pointer": "#/title",
-              "reportOnKey": false,
-              "source": "fixtures/resolve-refs-in-config/seo.yaml",
-            },
-          ],
-          "message": "Expected type \`string\` but got \`integer\`.",
-          "ruleId": "configuration spec",
-          "severity": "warn",
-          "suggest": [],
-        },
-        {
-          "from": {
-            "pointer": "#/rules",
-            "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
-          },
-          "location": [
-            {
-              "pointer": "#/non-existing-rule",
-              "reportOnKey": true,
-              "source": "fixtures/resolve-refs-in-config/rules.yaml",
-            },
-          ],
-          "message": "Property \`non-existing-rule\` is not expected here.",
-          "ruleId": "configuration spec",
-          "severity": "warn",
-          "suggest": [],
-        },
-        {
-          "location": [
-            {
-              "pointer": "#/theme",
-              "reportOnKey": false,
-              "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
-            },
-          ],
-          "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
-          "ruleId": "configuration no-unresolved-refs",
-          "severity": "warn",
-          "suggest": [],
-        },
-      ]
-    `);
   });
 });