Repository: Redocly/redocly-cli (ts)

Issue:
Remote schema errors are not being ignored by lint-ignore-file
**To Reproduce**

1. Open the attached **.zip** with dummy **openapi.yaml** file and configuration file(**.redocly.yaml**) inside: [cli-reproduce.zip](https://github.com/Redocly/redocly-cli/files/10757033/cli-reproduce.zip)

3. Note that in our **.redocly.yaml** we have an assertion, set to throw an error if object description is not provided:

![image](https://user-images.githubusercontent.com/59624019/219390625-62e34193-d1e0-4d6f-b82a-e9959e761a30.png)

4. Run `redocly lint openapi.yaml` command.
5. Notice that lint found 2 errors and 1 warning in total and 1 error is coming from the remote schema file(Order)
6. Generate lint ignore file by running `redocly lint openapi.yaml --generate-ignore-file`
7. Run `redocly lint openapi.yaml` afterward.
8. Notice that despite the fact that the lint ignore file has been generated properly, remote schema errors are not actually ignored and are causing validation errors:

![image](https://user-images.githubusercontent.com/59624019/219392839-9bca8bcf-dc2c-4b88-953a-0f40114cf46a.png)

9. Interesting fact, remote URL in generated lint ignore file has been generated with one forward slash:

![image](https://user-images.githubusercontent.com/59624019/219393937-ad308e5d-f852-4417-991f-971ae2dbe681.png)


**Expected behavior**
Remote schemas errors should be ignored by the lint ignore file as well as errors for the schemas placed locally. Feel free to reach out if more details or help is needed.

**Redocly Version(s)**
Redocly CLI version - `1.0.0-beta.123`

**`Node.js` Version(s)**
Node `v16.10.0`


<img src="https://front.com/assets/img/favicons/favicon-32x32.png" height="16" width="16" alt="Front logo" /> [Front conversations](https://app.frontapp.com/open/top_7i2do)

Pull request:
fix: add handling of absolute url path in ignore-file
## What/Why/How?
Fix bug if remote schema from added to ignore file.
Fix output for errors if its is url as a pointer
## Reference
Closes #1013
## Testing

## Screenshots (optional)
<img width="968" alt="Screenshot 2023-02-20 at 16 24 30" src="https://user-images.githubusercontent.com/52038205/220133044-fb53baef-add5-4ecc-92b6-0ef31a637aa0.png">
<img width="724" alt="Screenshot 2023-02-20 at 16 24 38" src="https://user-images.githubusercontent.com/52038205/220133054-709f324d-7fda-4e9b-88b3-c2191b2cf500.png">

## Check yourself

- [x] Code is linted
- [ ] 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


Patch:
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index dee78684..6957f1d8 100755
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -21,6 +21,7 @@ import type {
   ThemeRawConfig,
 } from './types';
 import { getResolveConfig } from './utils';
+import { isAbsoluteUrl } from '../ref-utils';
 
 export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
 const IGNORE_BANNER =
@@ -115,11 +116,17 @@ export class StyleguideConfig {
 
     // resolve ignore paths
     for (const fileName of Object.keys(this.ignore)) {
-      this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
+      this.ignore[
+        isAbsoluteUrl(fileName) ? fileName : path.resolve(path.dirname(ignoreFile), fileName)
+      ] = this.ignore[fileName];
+
       for (const ruleId of Object.keys(this.ignore[fileName])) {
         this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
       }
-      delete this.ignore[fileName];
+
+      if (!isAbsoluteUrl(fileName)) {
+        delete this.ignore[fileName];
+      }
     }
   }
 
@@ -128,8 +135,11 @@ export class StyleguideConfig {
     const ignoreFile = path.join(dir, IGNORE_FILE);
     const mapped: Record<string, any> = {};
     for (const absFileName of Object.keys(this.ignore)) {
-      const ignoredRules = (mapped[slash(path.relative(dir, absFileName))] =
-        this.ignore[absFileName]);
+      const mappedDefinitionName = isAbsoluteUrl(absFileName)
+        ? absFileName
+        : slash(path.relative(dir, absFileName));
+      const ignoredRules = (mapped[mappedDefinitionName] = this.ignore[absFileName]);
+
       for (const ruleId of Object.keys(ignoredRules)) {
         ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]) as any;
       }
diff --git a/packages/core/src/format/format.ts b/packages/core/src/format/format.ts
index d0670baa..26511879 100644
--- a/packages/core/src/format/format.ts
+++ b/packages/core/src/format/format.ts
@@ -7,6 +7,7 @@ const coreVersion = require('../../package.json').version;
 import { NormalizedProblem, ProblemSeverity, LineColLocationObject, LocationObject } from '../walk';
 import { getCodeframe, getLineColLocation } from './codeframes';
 import { env } from '../env';
+import { isAbsoluteUrl } from '../ref-utils';
 
 export type Totals = {
   errors: number;
@@ -120,7 +121,7 @@ export function formatProblems(
       for (const [file, { ruleIdPad, locationPad: positionPad, fileProblems }] of Object.entries(
         groupedByFile
       )) {
-        logger.info(`${colorize.blue(path.relative(cwd, file))}:\n`);
+        logger.info(`${colorize.blue(isAbsoluteUrl(file) ? file : path.relative(cwd, file))}:\n`);
 
         for (let i = 0; i < fileProblems.length; i++) {
           const problem = fileProblems[i];
@@ -138,7 +139,9 @@ export function formatProblems(
       output.write('<checkstyle version="4.3">\n');
 
       for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
-        output.write(`<file name="${xmlEscape(path.relative(cwd, file))}">\n`);
+        output.write(
+          `<file name="${xmlEscape(isAbsoluteUrl(file) ? file : path.relative(cwd, file))}">\n`
+        );
         fileProblems.forEach(formatCheckstyle);
         output.write(`</file>\n`);
       }
@@ -169,7 +172,9 @@ export function formatProblems(
       return {
         description: p.message,
         location: {
-          path: path.relative(cwd, location.source.absoluteRef),
+          path: isAbsoluteUrl(location.source.absoluteRef)
+            ? location.source.absoluteRef
+            : path.relative(cwd, location.source.absoluteRef),
           lines: {
             begin: lineCol.start.line,
           },
@@ -191,14 +196,18 @@ export function formatProblems(
           location: p.location.map((location: any) => ({
             ...location,
             source: {
-              ref: path.relative(cwd, location.source.absoluteRef),
+              ref: isAbsoluteUrl(location.source.absoluteRef)
+                ? location.source.absoluteRef
+                : path.relative(cwd, location.source.absoluteRef),
             },
           })),
           from: p.from
             ? {
                 ...p.from,
                 source: {
-                  ref: path.relative(cwd, p.from?.source.absoluteRef || cwd),
+                  ref: isAbsoluteUrl(p.from?.source.absoluteRef)
+                    ? p.from?.source.absoluteRef
+                    : path.relative(cwd, p.from?.source.absoluteRef || cwd),
                 },
               }
             : undefined,
@@ -226,7 +235,9 @@ export function formatProblems(
   function formatCodeframe(problem: NormalizedProblem, idx: number) {
     const bgColor = getBgColor(problem);
     const location = problem.location[0]; // TODO: support multiple locations
-    const relativePath = path.relative(cwd, location.source.absoluteRef);
+    const relativePath = isAbsoluteUrl(location.source.absoluteRef)
+      ? location.source.absoluteRef
+      : path.relative(cwd, location.source.absoluteRef);
     const loc = getLineColLocation(location);
     const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
     const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;


Test patch:
diff --git a/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap
new file mode 100644
index 00000000..7e65e0a4
--- /dev/null
+++ b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap
@@ -0,0 +1,144 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generation ignore object should generate config with absoluteUri for ignore 1`] = `
+StyleguideConfig {
+  "_usedRules": Set {},
+  "_usedVersions": Set {},
+  "configFile": undefined,
+  "decorators": Object {
+    "oas2": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "oas3_0": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "oas3_1": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+  },
+  "doNotResolveExamples": false,
+  "extendPaths": Array [],
+  "ignore": Object {
+    "https://some-path.yaml": Object {
+      "no-unused-components": Set {
+        "#/components/schemas/Foo",
+      },
+    },
+    "some-path/openapi.yaml": Object {
+      "no-unused-components": Set {
+        "#/components/schemas/Foo",
+      },
+    },
+  },
+  "pluginPaths": Array [],
+  "plugins": Array [],
+  "preprocessors": Object {
+    "oas2": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "oas3_0": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "oas3_1": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+  },
+  "rawConfig": Object {
+    "_usedRules": Set {},
+    "_usedVersions": Set {},
+    "configFile": "redocly.yaml",
+    "decorators": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "doNotResolveExamples": false,
+    "ignore": Object {},
+    "plugins": Array [],
+    "preprocessors": Object {
+      "oas2": Object {},
+      "oas3_0": Object {},
+      "oas3_1": Object {},
+    },
+    "rawConfig": Object {
+      "plugins": Array [],
+      "rules": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
+    "recommendedFallback": false,
+    "rules": Object {
+      "oas2": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_0": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_1": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
+  },
+  "recommendedFallback": false,
+  "rules": Object {
+    "oas2": Object {
+      "oas2": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_0": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_1": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
+    "oas3_0": Object {
+      "oas2": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_0": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_1": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
+    "oas3_1": Object {
+      "oas2": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_0": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_1": Object {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
+  },
+}
+`;
diff --git a/packages/core/src/config/__tests__/config.test.ts b/packages/core/src/config/__tests__/config.test.ts
index 059f7567..75c0c8d0 100644
--- a/packages/core/src/config/__tests__/config.test.ts
+++ b/packages/core/src/config/__tests__/config.test.ts
@@ -1,6 +1,17 @@
 import { OasVersion } from '../../oas-types';
 import { Config, StyleguideConfig } from '../config';
 import { getMergedConfig } from '../utils';
+import { doesYamlFileExist } from '../../utils';
+import { parseYaml } from '../../js-yaml';
+import { readFileSync } from 'fs';
+import { ignoredFileStub } from './fixtures/ingore-file';
+import * as path from 'path';
+import { NormalizedProblem } from '../../walk';
+import { Source } from '../../resolve';
+
+jest.mock('../../utils');
+jest.mock('../../js-yaml');
+jest.mock('fs');
 
 const testConfig: Config = {
   rawConfig: {
@@ -280,3 +291,17 @@ describe('StyleguideConfig.extendTypes', () => {
     );
   });
 });
+
+describe('generation ignore object', () => {
+  it('should generate config with absoluteUri for ignore', () => {
+    (readFileSync as jest.Mock<any, any>).mockImplementationOnce(() => '');
+    (parseYaml as jest.Mock<any, any>).mockImplementationOnce(() => ignoredFileStub);
+    (doesYamlFileExist as jest.Mock<any, any>).mockImplementationOnce(() => true);
+
+    jest.spyOn(path, 'resolve').mockImplementationOnce((_, filename) => `some-path/${filename}`);
+
+    const styleguideConfig = new StyleguideConfig(testConfig.styleguide);
+
+    expect(styleguideConfig).toMatchSnapshot();
+  });
+});
diff --git a/packages/core/src/config/__tests__/fixtures/ingore-file.ts b/packages/core/src/config/__tests__/fixtures/ingore-file.ts
new file mode 100644
index 00000000..fbb543a1
--- /dev/null
+++ b/packages/core/src/config/__tests__/fixtures/ingore-file.ts
@@ -0,0 +1,8 @@
+export const ignoredFileStub = {
+  'openapi.yaml': {
+    'no-unused-components': ['#/components/schemas/Foo'],
+  },
+  'https://some-path.yaml': {
+    'no-unused-components': ['#/components/schemas/Foo'],
+  },
+};