Repository: stoplightio/spectral
Pull request #1675 (2021-06-16 13:42:43)

chore: drop exceptions
Will reduce the noise in #1559 

Do note that although exceptions are gone, they'll be replaced with #1021 which'll be actually a superset of present exceptions, so we're not losing any functionality.

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [x] Yes
- [ ] No



P0lip: > Only thing I would add is that the documentation should also be removed from meta.

This is going to happen automatically thanks to web hooks :)

Patch:
diff --git a/docs/guides/4-custom-rulesets.md b/docs/guides/4-custom-rulesets.md
index 520d5be4..a0b1cdf7 100644
--- a/docs/guides/4-custom-rulesets.md
+++ b/docs/guides/4-custom-rulesets.md
@@ -156,8 +156,6 @@ The example above will run all of the rules defined in the `spectral:oas` rulese
 
 <!-- theme: info -->
 
-> If you'd like to disable rules for a specific file or part of a file, you can use [Exceptions](6-exceptions.md).
-
 ## Enabling Rules
 
 Sometimes you might want to apply specific rules from another ruleset. Use the `extends` property, and pass `off` as the second argument in order to add the rules from another ruleset, but disable them all by default. This allows you to pick and choose which rules you would like to enable.
diff --git a/docs/guides/6-exceptions.md b/docs/guides/6-exceptions.md
deleted file mode 100644
index 3c765591..00000000
--- a/docs/guides/6-exceptions.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# Exceptions
-
-From time to time, you want to ignore some specific results without [turning off the rule entirely](./4-custom-rulesets.md#disabling-rules). For instance, you may want to do this when working on a project that has several legacy APIs which are nowhere near the standards of your modern APIs, and you cannot fix all the errors for a specific file all at once.
-
-The ruleset can be extended for that purpose through the optional `except` property. `except` describes a map of locations (expressed as paths and [JSON Pointers](https://tools.ietf.org/html/rfc6901)) and rules that should be ignored.
-
-Locations be can either described as relative to the ruleset or absolute paths.
-
-```yaml
-extends: spectral:oas
-
-except:
-  "subfolder/one.yaml#":
-    - no-$ref-siblings
-  "/tmp/docs/one.yaml#/info":
-    - info-contact
-    - info-description
-```
-
-Doing this in a ruleset which is being distributed to multiple APIs is possible, but might not be what you want to do. Maybe the files your writing exceptions for do not exist in other projects, or maybe they do not need this exception being added.
-
-Instead, let's create two rulesets.
-
-One we'll call the main ruleset which is the one we're distributing via a URL, NPM, or some other means. This ruleset will describe the ideal set of rules:
-
-```yaml
-# https://acme.org/ruleset.yaml
-extends: spectral:oas
-
-rules:
-  # all your custom rules
-```
-
-Then a second ruleset will be created in project root, which will be created in the project with the appropriate extensions. Assuming you chose a URL to distribute the ruleset, it would look like this:
-
-```yaml
-# .spectral.yaml
-extends: https://acme.org/ruleset.yaml
-
-except:
-  "subfolder/one.yaml#":
-    - oas3-api-servers
-  "/tmp/docs/one.yaml#/info":
-    - info-contact
-    - info-description
-```
-
-Done!
-
-In the future when the API is improved to pass those rules, they can be removed. If they get everything done the project ruleset will just look like this:
-
-```yaml
-extends: https://acme.org/ruleset.yaml
-```
-
-If you wish to turn off a given rule for a whole file, you should skip the pointer.
-
-```yaml
-# .spectral.yaml
-extends: https://acme.org/ruleset.yaml
-
-except:
-  "subfolder/one.yaml":
-    - my-rule
-```
-
-Disabling a particular JSON path globally is supported as well.
-
-```yaml
-extends: https://acme.org/ruleset.yaml
-
-except:
-  "#/info":
-    - info-contact
-```
-
-## Special Characters
-
-[RFC 6901](https://tools.ietf.org/html/rfc6901#section-3) says that special characters
-`~` and `/` must be escaped as `~0` and `~1` respectively where they
-appear in a path. E.g.: the location of the `get` method from a
-`/todos` path in OpenAPI document `/root/here.yaml` would be expressed as `/root/here.yaml#/paths/~1todos/get`.
-
-An example with more path segments: the location for a `get` method from a
-`/todos/{todo_id}/labels` path in an openapi document `/root/here.yaml`
-would be expressed as `/root/here.yaml#/paths/~1todos~1{todo_id}~1labels/get`.
-
-<!-- theme: info -->
-
-> Running Spectral CLI with the `--format json` parameter is pretty useful to find out the path segments of each result.
diff --git a/src/cli/services/linter/utils/getRuleset.ts b/src/cli/services/linter/utils/getRuleset.ts
index fab02d5f..43dbbb77 100644
--- a/src/cli/services/linter/utils/getRuleset.ts
+++ b/src/cli/services/linter/utils/getRuleset.ts
@@ -10,7 +10,6 @@ async function loadRulesets(cwd: string, rulesetFiles: string[], opts: IRulesetR
     return {
       functions: {},
       rules: {},
-      exceptions: {},
     };
   }
 
diff --git a/src/meta/ruleset.schema.json b/src/meta/ruleset.schema.json
index a9608e2e..0aecc82d 100644
--- a/src/meta/ruleset.schema.json
+++ b/src/meta/ruleset.schema.json
@@ -42,16 +42,6 @@
         ]
       }
     },
-    "except": {
-      "type": "object",
-      "additionalProperties": {
-        "type": "array",
-        "items": {
-          "type": "string"
-        }
-      },
-      "errorMessage": "must be a map where each key is either a path or a path+json-pointer or a json-pointer and the value is an array of rules"
-    },
     "parserOptions": {
       "type": "object",
       "properties": {
diff --git a/src/ruleset/mergers/exceptions.ts b/src/ruleset/mergers/exceptions.ts
deleted file mode 100644
index 59c6fd24..00000000
--- a/src/ruleset/mergers/exceptions.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { extractPointerFromRef, extractSourceFromRef, pointerToPath } from '@stoplight/json';
-import { isAbsolute, join, normalize as pathNormalize } from '@stoplight/path';
-import { RulesetExceptionCollection } from '../../types/ruleset';
-
-export class InvalidUriError extends Error {
-  constructor(message: string) {
-    super(message);
-  }
-}
-
-const normalize = ($ref: string, rulesetUri?: string): string => {
-  const source = extractSourceFromRef($ref);
-
-  if (rulesetUri === void 0 && source !== null && !isAbsolute(source)) {
-    throw new InvalidUriError(
-      buildInvalidUriErrorMessage(
-        $ref,
-        rulesetUri,
-        'Only absolute Uris are allowed when no base ruleset uri has been provided',
-      ),
-    );
-  }
-
-  const pointer = extractPointerFromRef($ref);
-
-  if (source === null && pointer === null) {
-    throw new InvalidUriError(
-      `Malformed exception key (${$ref}). You must provide either a valid pointer or a valid source, or a source with a pointer.`,
-    );
-  }
-
-  if (pointer !== null) {
-    try {
-      pointerToPath(pointer);
-    } catch {
-      throw new InvalidUriError(buildInvalidUriErrorMessage($ref, rulesetUri));
-    }
-  }
-
-  if (source === null) {
-    return pointer as string;
-  }
-
-  const path = rulesetUri === undefined || isAbsolute(source) ? source : join(rulesetUri, '..', source);
-
-  return pathNormalize(path) + (pointer ?? '');
-};
-
-const buildErrorMessagePrefix = ($ref: string, rulesetUri?: string): string => {
-  let prefix = '';
-
-  if (rulesetUri !== void 0) {
-    prefix += `in ruleset \`${rulesetUri}\`, `;
-  }
-
-  return prefix + `\`except\` entry (key \`${$ref}\`) is malformed. `;
-};
-
-const buildInvalidUriErrorMessage = ($ref: string, rulesetUri?: string, precision?: string): string => {
-  return (
-    buildErrorMessagePrefix($ref, rulesetUri) +
-    `Key \`${$ref}\` is not a valid uri${precision !== void 0 ? ` (${precision})` : ''}.`
-  );
-};
-
-export function mergeExceptions(
-  target: RulesetExceptionCollection,
-  source: RulesetExceptionCollection,
-  baseUri?: string,
-): void {
-  for (const [location, sourceRules] of Object.entries(source)) {
-    const normalizedLocation = normalize(location, baseUri);
-    const targetRules = normalizedLocation in target ? target[normalizedLocation] : [];
-
-    const set = new Set(targetRules);
-
-    if (sourceRules.length === 0) {
-      throw new Error(buildErrorMessagePrefix(location, baseUri) + 'An empty array of rules has been provided.');
-    }
-
-    for (const rule of sourceRules) {
-      if (rule.length === 0) {
-        throw new Error(buildErrorMessagePrefix(location, baseUri) + 'A rule with an empty name has been provided.');
-      }
-
-      set.add(rule);
-    }
-
-    target[normalizedLocation] = [...set].sort((a, b) => a.localeCompare(b));
-  }
-}
diff --git a/src/ruleset/readRuleset.ts b/src/ruleset/readRuleset.ts
index 23a67607..329a4bb2 100644
--- a/src/ruleset/readRuleset.ts
+++ b/src/ruleset/readRuleset.ts
@@ -7,7 +7,6 @@ import { createHttpAndFileResolver, IHttpAndFileResolverOptions } from '../resol
 import { FileRulesetSeverity, IRuleset, RulesetFunctionCollection } from '../types/ruleset';
 import { findFile, isNPMSource } from './utils';
 import { mergeFormats, mergeFunctions, mergeRules } from './mergers';
-import { mergeExceptions } from './mergers/exceptions';
 import { assertValidRuleset, isValidRule } from './validation';
 import { parseYaml } from '../parsers';
 
@@ -27,7 +26,6 @@ export async function readRuleset(uris: string | string[], opts?: IRulesetReadOp
   const base: IRuleset = {
     rules: {},
     functions: {},
-    exceptions: {},
   };
 
   const processedRulesets = new Set<string>();
@@ -39,7 +37,6 @@ export async function readRuleset(uris: string | string[], opts?: IRulesetReadOp
     if (resolvedRuleset === null) continue;
     Object.assign(base.rules, resolvedRuleset.rules);
     Object.assign(base.functions, resolvedRuleset.functions);
-    Object.assign(base.exceptions, resolvedRuleset.exceptions);
 
     if (resolvedRuleset.parserOptions !== void 0 && !('parserOptions' in base)) {
       base.parserOptions = resolvedRuleset.parserOptions;
@@ -101,11 +98,9 @@ const createRulesetProcessor = (
     const ruleset = assertValidRuleset(JSON.parse(JSON.stringify(result)));
     const rules = {};
     const functions = {};
-    const exceptions = {};
     const newRuleset: IRuleset = {
       rules,
       functions,
-      exceptions,
       ...('parserOptions' in ruleset ? { parserOptions: ruleset.parserOptions } : null),
     };
 
@@ -126,7 +121,6 @@ const createRulesetProcessor = (
         if (extendedRuleset !== null) {
           mergeRules(rules, extendedRuleset.rules, parentSeverity);
           Object.assign(functions, extendedRuleset.functions);
-          mergeExceptions(exceptions, extendedRuleset.exceptions, baseUri);
         }
       }
     }
@@ -143,10 +137,6 @@ const createRulesetProcessor = (
       }
     }
 
-    if (ruleset.except !== void 0) {
-      mergeExceptions(exceptions, ruleset.except, baseUri);
-    }
-
     if (Array.isArray(ruleset.formats)) {
       mergeFormats(rules, ruleset.formats);
     }
diff --git a/src/runner/lintNode.ts b/src/runner/lintNode.ts
index 9ad5a4f6..a591559f 100644
--- a/src/runner/lintNode.ts
+++ b/src/runner/lintNode.ts
@@ -1,4 +1,4 @@
-import { JsonPath, Optional } from '@stoplight/types';
+import { JsonPath } from '@stoplight/types';
 import { get } from 'lodash';
 
 import { Document } from '../document';
@@ -7,14 +7,9 @@ import { getDiagnosticSeverity } from '../ruleset/severity';
 import { IFunctionResult, IFunctionValues, IGivenNode } from '../types';
 import { decodeSegmentFragment, getClosestJsonPath, printPath, PrintStyle } from '../utils';
 import { IRunnerInternalContext } from './types';
-import { getLintTargets, ExceptionLocation, isAKnownException, MessageVars, message } from './utils';
+import { getLintTargets, MessageVars, message } from './utils';
 
-export const lintNode = (
-  context: IRunnerInternalContext,
-  node: IGivenNode,
-  rule: Rule,
-  exceptionLocations: Optional<ExceptionLocation[]>,
-): void => {
+export const lintNode = (context: IRunnerInternalContext, node: IGivenNode, rule: Rule): void => {
   const fnContext: IFunctionValues = {
     original: node.value,
     given: node.value,
@@ -56,7 +51,6 @@ export const lintNode = (
                   context,
                   results,
                   rule,
-                  exceptionLocations,
                   targetPath, // todo: get rid of it somehow.
                 ),
           ),
@@ -66,7 +60,6 @@ export const lintNode = (
           context,
           targetResults,
           rule,
-          exceptionLocations,
           targetPath, // todo: get rid of it somehow.
         );
       }
@@ -78,7 +71,6 @@ function processTargetResults(
   context: IRunnerInternalContext,
   results: IFunctionResult[],
   rule: Rule,
-  exceptionLocations: Optional<ExceptionLocation[]>,
   targetPath: JsonPath,
 ): void {
   for (const result of results) {
@@ -90,10 +82,6 @@ function processTargetResults(
     const path = associatedItem?.path ?? getClosestJsonPath(context.documentInventory.resolved, escapedJsonPath);
     const source = associatedItem?.document.source;
 
-    if (exceptionLocations !== void 0 && isAKnownException(path, source, exceptionLocations)) {
-      continue;
-    }
-
     const document = associatedItem?.document ?? context.documentInventory.document;
     const range = document.getRangeForJsonPath(path, true) ?? Document.DEFAULT_RANGE;
     const value: unknown = path.length === 0 ? document.data : get(document.data, path);
diff --git a/src/runner/runner.ts b/src/runner/runner.ts
index 626749bf..ef9b7446 100644
--- a/src/runner/runner.ts
+++ b/src/runner/runner.ts
@@ -1,37 +1,17 @@
-import { DiagnosticSeverity, Optional } from '@stoplight/types';
 import { JSONPath, JSONPathCallback } from 'jsonpath-plus';
 import { flatMap, isObject } from 'lodash';
 import { JSONPathExpression, traverse } from 'nimma';
 
-import { IDocument, STDIN } from '../document';
+import { IDocument } from '../document';
 import { DocumentInventory } from '../documentInventory';
 import { OptimizedRule, Rule } from '../rule';
 import { IGivenNode, IRuleResult } from '../types';
 import { ComputeFingerprintFunc, prepareResults } from '../utils';
-import { generateDocumentWideResult } from '../utils/generateDocumentWideResult';
 import { lintNode } from './lintNode';
 import { RunnerRuntime } from './runtime';
 import { IRunnerInternalContext, IRunnerPublicContext } from './types';
-import { ExceptionLocation, pivotExceptions } from './utils';
 
-const isStdInSource = (inventory: DocumentInventory): boolean => {
-  return inventory.document.source === STDIN;
-};
-
-const generateDefinedExceptionsButStdIn = (documentInventory: DocumentInventory): IRuleResult => {
-  return generateDocumentWideResult(
-    documentInventory.document,
-    'The ruleset contains `except` entries. However, they cannot be enforced when the input is passed through stdin.',
-    DiagnosticSeverity.Warning,
-    'except-but-stdin',
-  );
-};
-
-const runRule = (
-  context: IRunnerInternalContext,
-  rule: Rule,
-  exceptRuleByLocations: Optional<ExceptionLocation[]>,
-): void => {
+const runRule = (context: IRunnerInternalContext, rule: Rule): void => {
   const target = rule.resolved ? context.documentInventory.resolved : context.documentInventory.unresolved;
 
   for (const given of rule.given) {
@@ -44,7 +24,6 @@ const runRule = (
           value: target,
         },
         rule,
-        exceptRuleByLocations,
       );
     } else if (isObject(target)) {
       JSONPath({
@@ -61,7 +40,6 @@ const runRule = (
               value: result.value,
             },
             rule,
-            exceptRuleByLocations,
           );
         }) as JSONPathCallback,
       });
@@ -89,7 +67,7 @@ export class Runner {
 
     const { inventory: documentInventory } = this;
 
-    const { rules, exceptions } = context;
+    const { rules } = context;
 
     const runnerContext: IRunnerInternalContext = {
       ...context,
@@ -98,13 +76,6 @@ export class Runner {
       promises: [],
     };
 
-    const isStdIn = isStdInSource(documentInventory);
-    const exceptRuleByLocations = isStdIn ? {} : pivotExceptions(exceptions, rules);
-
-    if (isStdIn && Object.keys(exceptions).length > 0) {
-      runnerContext.results.push(generateDefinedExceptionsButStdIn(documentInventory));
-    }
-
     const relevantRules = Object.values(rules).filter(
       rule => rule.enabled && rule.matchesFormat(documentInventory.formats),
     );
@@ -114,7 +85,7 @@ export class Runner {
     const unoptimizedRules: Rule[] = [];
 
     const traverseCb = (rule: OptimizedRule, node: IGivenNode): void => {
-      lintNode(runnerContext, node, rule, exceptRuleByLocations[rule.name]);
+      lintNode(runnerContext, node, rule);
     };
 
     for (const rule of relevantRules) {
@@ -141,7 +112,7 @@ export class Runner {
     }
 
     for (const rule of unoptimizedRules) {
-      runRule(runnerContext, rule, exceptRuleByLocations[rule.name]);
+      runRule(runnerContext, rule);
     }
 
     this.runtime.emit('beforeTeardown');
diff --git a/src/runner/types.ts b/src/runner/types.ts
index 94adb495..55dd8321 100644
--- a/src/runner/types.ts
+++ b/src/runner/types.ts
@@ -1,11 +1,9 @@
 import { DocumentInventory } from '../documentInventory';
 import { FunctionCollection, IRuleResult, RunRuleCollection } from '../types';
-import { RulesetExceptionCollection } from '../types/ruleset';
 
 export interface IRunnerPublicContext {
   rules: RunRuleCollection;
   functions: FunctionCollection;
-  exceptions: RulesetExceptionCollection;
 }
 
 export interface IRunnerInternalContext extends IRunnerPublicContext {
diff --git a/src/runner/utils/index.ts b/src/runner/utils/index.ts
index 6ae63df5..1c5db3cf 100644
--- a/src/runner/utils/index.ts
+++ b/src/runner/utils/index.ts
@@ -1,5 +1,3 @@
 export * from './arePathsEqual';
 export * from './getLintTargets';
-export * from './isAKnownException';
 export * from './message';
-export * from './pivotExceptions';
diff --git a/src/runner/utils/isAKnownException.ts b/src/runner/utils/isAKnownException.ts
deleted file mode 100644
index b62f2b75..00000000
--- a/src/runner/utils/isAKnownException.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { JsonPath, Optional } from '@stoplight/types';
-import { arePathsEqual } from './arePathsEqual';
-import { ExceptionLocation } from './pivotExceptions';
-
-export const isAKnownException = (
-  path: JsonPath,
-  source: Optional<string | null>,
-  locations: ExceptionLocation[],
-): boolean => {
-  for (const location of locations) {
-    if (location.source !== null && source !== location.source) {
-      continue;
-    }
-
-    if (location.path === null) {
-      // a rule is turned off for a whole file
-      return true;
-    }
-
-    if (arePathsEqual(path, location.path)) {
-      return true;
-    }
-  }
-
-  return false;
-};
diff --git a/src/runner/utils/pivotExceptions.ts b/src/runner/utils/pivotExceptions.ts
deleted file mode 100644
index 6c6d19d2..00000000
--- a/src/runner/utils/pivotExceptions.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { extractPointerFromRef, extractSourceFromRef, pointerToPath } from '@stoplight/json';
-import { Dictionary, JsonPath } from '@stoplight/types';
-import { RunRuleCollection } from '../../types';
-import { RulesetExceptionCollection } from '../../types/ruleset';
-import { InvalidUriError } from '../../ruleset/mergers/exceptions';
-
-export type ExceptionLocation =
-  | {
-      source: string;
-      path: JsonPath;
-    }
-  | {
-      source: string;
-      path: null;
-    }
-  | {
-      source: null;
-      path: JsonPath;
-    };
-
-export const pivotExceptions = (
-  exceptions: RulesetExceptionCollection,
-  runRules: RunRuleCollection,
-): Dictionary<ExceptionLocation[], string> => {
-  const dic: Dictionary<ExceptionLocation[], string> = {};
-
-  for (const [location, rules] of Object.entries(exceptions)) {
-    const pointer = extractPointerFromRef(location);
-    const source = extractSourceFromRef(location);
-
-    if (pointer === null && source === null) {
-      throw new InvalidUriError(`Malformed exception key (${location}).`);
-    }
-
-    for (const rulename of rules) {
-      const rule = runRules[rulename];
-
-      if (rule !== void 0) {
-        if (!(rulename in dic)) {
-          dic[rulename] = [];
-        }
-
-        dic[rulename].push({
-          source,
-          path: pointer === null ? null : pointerToPath(pointer),
-        } as ExceptionLocation);
-      }
-    }
-  }
-
-  return dic;
-};
diff --git a/src/spectral.ts b/src/spectral.ts
index 2cb8cc9d..25f26ca0 100644
--- a/src/spectral.ts
+++ b/src/spectral.ts
@@ -15,7 +15,6 @@ import request from './request';
 import { createHttpAndFileResolver } from './resolvers/http-and-file';
 import { OptimizedRule, Rule } from './rule';
 import { compileExportedFunction, IRulesetReadOptions, readRuleset, getDiagnosticSeverity } from './ruleset';
-import { mergeExceptions } from './ruleset/mergers/exceptions';
 import { Runner, RunnerRuntime } from './runner';
 import {
   FormatLookup,
@@ -30,7 +29,7 @@ import {
   RuleCollection,
   RunRuleCollection,
 } from './types';
-import { IParserOptions, IRuleset, RulesetExceptionCollection } from './types/ruleset';
+import { IParserOptions, IRuleset } from './types/ruleset';
 import { ComputeFingerprintFunc, defaultComputeResultFingerprint, empty, isNimmaEnvVariableSet } from './utils';
 import { generateDocumentWideResult } from './utils/generateDocumentWideResult';
 import { DEFAULT_PARSER_OPTIONS } from './consts';
@@ -45,7 +44,6 @@ export class Spectral {
 
   public readonly functions: FunctionCollection & CoreFunctions = { ...coreFunctions };
   public readonly rules: RunRuleCollection = {};
-  public readonly exceptions: RulesetExceptionCollection = {};
   public readonly formats: RegisteredFormats;
   public readonly parserOptions: Required<IParserOptions> = { ...DEFAULT_PARSER_OPTIONS };
 
@@ -147,7 +145,6 @@ export class Spectral {
     await runner.run({
       rules: this.rules,
       functions: this.functions,
-      exceptions: this.exceptions,
     });
 
     const results = runner.getResults(this._computeFingerprint);
@@ -197,15 +194,6 @@ export class Spectral {
     }
   }
 
-  private setExceptions(exceptions: RulesetExceptionCollection): void {
-    const target: RulesetExceptionCollection = {};
-    mergeExceptions(target, exceptions);
-
-    empty(this.exceptions);
-
-    Object.assign(this.exceptions, target);
-  }
-
   public async loadRuleset(uris: string[] | string, options?: IRulesetReadOptions): Promise<void> {
     this.setRuleset(await readRuleset(Array.isArray(uris) ? uris : [uris], { agent: this.agent, ...options }));
   }
@@ -242,8 +230,6 @@ export class Spectral {
       }, {}),
     );
 
-    this.setExceptions(ruleset.exceptions);
-
     if (ruleset.parserOptions !== void 0) {
       Object.assign<Required<IParserOptions>, IParserOptions>(this.parserOptions, ruleset.parserOptions);
     }
diff --git a/src/types/ruleset.ts b/src/types/ruleset.ts
index 3acbc529..d3e71bd1 100644
--- a/src/types/ruleset.ts
+++ b/src/types/ruleset.ts
@@ -18,7 +18,6 @@ export interface IRulesetFunctionDefinition {
 }
 
 export type RulesetFunctionCollection = Dictionary<IRulesetFunctionDefinition, string>;
-export type RulesetExceptionCollection = Dictionary<string[], string>;
 
 export interface IParserOptions {
   duplicateKeys?: DiagnosticSeverity | HumanReadableDiagnosticSeverity;
@@ -28,7 +27,6 @@ export interface IParserOptions {
 export interface IRuleset {
   rules: RuleCollection;
   functions: RulesetFunctionCollection;
-  exceptions: RulesetExceptionCollection;
   parserOptions?: IParserOptions;
 }
 
@@ -39,6 +37,5 @@ export interface IRulesetFile {
   rules?: FileRuleCollection;
   functionsDir?: string;
   functions?: string[];
-  except?: RulesetExceptionCollection;
   parserOptions?: IParserOptions;
 }


Test patch:
diff --git a/src/__tests__/__fixtures__/rulesets/bare.json b/src/__tests__/__fixtures__/rulesets/bare.json
index bd98dc4a..3c980184 100644
--- a/src/__tests__/__fixtures__/rulesets/bare.json
+++ b/src/__tests__/__fixtures__/rulesets/bare.json
@@ -13,12 +13,5 @@
         }
       }
     }
-  },
-  "except": {
-    "/test/file.json#/info": ["info-contact", "info-description"],
-    "/test/file.json#": [ "oas3-api-servers"],
-    "/test/file.json#/paths/~1a.two/get": ["operation-success-response"],
-    "/test/file.json#/paths/~1b.three/get": ["operation-success-response"],
-    "another.yaml#": ["dummy-rule", "info-contact"]
   }
 }
diff --git a/src/__tests__/__fixtures__/rulesets/exceptions-resolving.json b/src/__tests__/__fixtures__/rulesets/exceptions-resolving.json
deleted file mode 100644
index e490f823..00000000
--- a/src/__tests__/__fixtures__/rulesets/exceptions-resolving.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  "rules": {
-    "no-yaml-remote-reference": {
-      "message": "$ref must not point at yaml files",
-      "given": "$..$ref",
-      "resolved": false,
-      "then": {
-        "function": "pattern",
-        "functionOptions": {
-          "notMatch": "\\.yaml(#.*)$"
-        }
-      }
-    },
-    "no-remote-reference": {
-      "message": "$ref must not point at remote files",
-      "given": "$..$ref",
-      "recommended": true,
-      "resolved": false,
-      "then": {
-        "function": "pattern",
-        "functionOptions": {
-          "notMatch": "\\.[a-z]*(#.*)$"
-        }
-      }
-    },
-    "strings-maxLength": {
-      "message": "String typed properties MUST be further described using 'maxLength'. Error: {{error}}",
-      "given": "$..*[?(@.type === 'string')]",
-      "then": {
-        "field": "maxLength",
-        "function": "truthy"
-      }
-    },
-    "schema": {
-      "message": "I always throw!",
-      "given": "$",
-      "then": {
-        "function": "falsy"
-      }
-    },
-    "no-json-schema-integer-type": {
-      "given": "$.components.schemas..type",
-      "then": {
-        "function": "pattern",
-        "functionOptions": {
-          "notMatch": "^integer$"
-        }
-      }
-    }
-  },
-  "except": {
-    "../exceptions.remote.oas3.yaml#/components/schemas/TheRemoteType": ["strings-maxLength"],
-    "../../foo.json#/components/schemas/TheLocalType/$ref": ["no-yaml-remote-reference"],
-    "../../foo.json#/components/schemas/integerRemote/$ref": ["no-yaml-remote-reference"],
-    "../../foo.json": ["no-remote-reference"],
-    "another.yaml#": ["dummy-rule", "info-contact"],
-    "#/components/schemas/integerOne/type": ["no-json-schema-integer-type"]
-  }
-}
diff --git a/src/__tests__/__fixtures__/rulesets/exceptions.json b/src/__tests__/__fixtures__/rulesets/exceptions.json
deleted file mode 100644
index a37b8180..00000000
--- a/src/__tests__/__fixtures__/rulesets/exceptions.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "rules": {
-    "info-matches-stoplight": {
-      "message": "Info must contain Stoplight",
-      "given": "$",
-      "then": {
-        "field": "info.title",
-        "function": "pattern",
-        "functionOptions": {
-          "match": "Stoplight"
-        }
-      }
-    },
-    "info-contact": {
-      "description": "Info object should contain `contact` object.",
-      "given": "$",
-      "then": {
-        "field": "info.contact",
-        "function": "truthy"
-      }
-    },
-    "info-description": {
-      "description": "OpenAPI object info `description` must be present and non-empty string.",
-      "given": "$",
-      "then": {
-        "field": "info.description",
-        "function": "truthy"
-      }
-    },
-    "oas3-api-servers": {
-      "description": "OpenAPI `servers` must be present and non-empty array.",
-      "given": "$",
-      "then": {
-        "field": "servers",
-        "function": "schema",
-        "functionOptions": {
-          "schema": {
-            "items": {
-              "type": "object"
-            },
-            "minItems": 1,
-            "type": "array"
-          }
-        }
-      }
-    },
-    "operation-success-response": {
-      "description": "Operation must have at least one `2xx` or `3xx` response.",
-      "given": "$.paths.*[?( @property === 'get' || @property === 'put' || @property === 'post' || @property === 'delete' || @property === 'options' || @property === 'head' || @property === 'patch' || @property === 'trace' )]",
-      "then": {
-        "function": "schema",
-        "functionOptions": {
-          "schema": {
-            "type": "object",
-            "required": ["200"],
-            "properties": {
-              "200": {
-                "type": "object"
-              }
-            }
-          }
-        }
-      }
-    }
-  },
-  "except": {
-    "/test/file.json#/info": ["info-contact", "info-description"],
-    "/test/file.json#": ["oas3-api-servers"],
-    "/test/file.json#/paths/~1a.two/get": ["operation-success-response"],
-    "/test/file.json#/paths/~1b.three/get": ["operation-success-response"],
-    "another.yaml#": ["dummy-rule", "info-contact"]
-  }
-}
diff --git a/src/__tests__/linter.jest.test.ts b/src/__tests__/linter.jest.test.ts
index ec58947e..a0830c8d 100644
--- a/src/__tests__/linter.jest.test.ts
+++ b/src/__tests__/linter.jest.test.ts
@@ -6,12 +6,9 @@ import * as path from 'path';
 import * as timers from 'timers';
 
 import { httpAndFileResolver } from '../resolvers/http-and-file';
-import { readRuleset } from '../ruleset';
 import { IFunctionResult, Spectral } from '../spectral';
-import { IRuleset, RulesetExceptionCollection } from '../types/ruleset';
 
 const functionRuleset = path.join(__dirname, './__fixtures__/rulesets/custom-functions.json');
-const exceptionRuleset = path.join(__dirname, './__fixtures__/rulesets/exceptions.json');
 const customDirectoryFunctionsRuleset = path.join(__dirname, './__fixtures__/rulesets/custom-directory-function.json');
 const recommendedRulesetPath = path.join(__dirname, './__fixtures__/rulesets/recommended.json');
 
@@ -98,7 +95,6 @@ describe('Linter', () => {
       const scope = nock('https://stoplight.io').get('/').once().reply(200);
 
       spectral.setRuleset({
-        exceptions: {},
         functions: {
           fn: {
             source: null,
@@ -196,7 +192,6 @@ describe('Linter', () => {
 
       it('should be able to make actual requests', async () => {
         spectral.setRuleset({
-          exceptions: {},
           functions: {
             [fnName]: {
               name: fnName,
@@ -269,7 +264,6 @@ module.exports = async function (targetVal) {
 
       it('should be able to defer request and make it in beforeTeardown step', async () => {
         spectral.setRuleset({
-          exceptions: {},
           functions: {
             [fnName]: {
               name: fnName,
@@ -388,254 +382,6 @@ module.exports = async function (targetVal) {
     );
   });
 
-  describe('Exceptions handling', () => {
-    it('should ignore specified rules violations in a standalone document', async () => {
-      await spectral.loadRuleset(exceptionRuleset);
-
-      const res = await spectral.run(
-        {
-          openapi: '3.0.2',
-          info: {
-            title: '',
-          },
-        },
-        {
-          resolve: {
-            documentUri: '/test/file.json',
-          },
-        },
-      );
-
-      expect(res.length).toBeGreaterThan(0);
-
-      expect(res).not.toContainEqual(
-        expect.objectContaining({
-          code: 'info-contact',
-        }),
-      );
-
-      expect(res).not.toContainEqual(
-        expect.objectContaining({
-          code: 'info-description',
-        }),
-      );
-
-      expect(res).not.toContainEqual(
-        expect.objectContaining({
-          code: 'oas3-api-servers',
-        }),
-      );
-    });
-
-    it('should not swallow results', async () => {
-      // Cf. https://github.com/stoplightio/spectral/issues/1018
-
-      const document = {
-        openapi: '3.0.2',
-        paths: {
-          '/a.one': { get: 17 },
-          '/a.two': { get: 18 },
-          '/a.three': { get: 19 },
-          '/b.one': { get: 17 },
-          '/b.two': { get: 18 },
-          '/b.three': { get: 19 },
-        },
-      };
-
-      await spectral.loadRuleset(exceptionRuleset);
-
-      const res = await spectral.run(document, {
-        resolve: {
-          documentUri: '/test/file.json',
-        },
-      });
-
-      expect(res).toEqual(
-        expect.arrayContaining([
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/a.one', 'get'],
-            source: '/test/file.json',
-          }),
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/a.three', 'get'],
-            source: '/test/file.json',
-          }),
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/b.one', 'get'],
-            source: '/test/file.json',
-          }),
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/b.two', 'get'],
-            source: '/test/file.json',
-          }),
-        ]),
-      );
-
-      expect(res).toEqual(
-        expect.not.arrayContaining([
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/a.two', 'get'],
-            source: '/test/file.json',
-          }),
-        ]),
-      );
-
-      expect(res).toEqual(
-        expect.not.arrayContaining([
-          expect.objectContaining({
-            code: 'operation-success-response',
-            message: 'Operation must have at least one `2xx` or `3xx` response.',
-            path: ['paths', '/b.three', 'get'],
-            source: '/test/file.json',
-          }),
-        ]),
-      );
-    });
-
-    describe('resolving', () => {
-      const document = {
-        openapi: '3.0.2',
-        components: {
-          schemas: {
-            TheLocalType: {
-              $ref: './__fixtures__/exceptions.remote.oas3.yaml#/components/schemas/TheRemoteType',
-            },
-            integerOne: {
-              type: 'integer',
-            },
-            integerRemote: {
-              $ref: './__fixtures__/exceptions.remote.oas3.yaml#/components/schemas/integerOne',
-            },
-            integerTwo: {
-              type: 'integer',
-            },
-          },
-        },
-      };
-
-      let testRuleset: IRuleset;
-
-      beforeAll(async () => {
-        testRuleset = await readRuleset(path.join(__dirname, './__fixtures__/rulesets/exceptions-resolving.json'));
-      });
-
-      const opts = {
-        resolve: {
-          documentUri: path.join(__dirname, './foo.json'),
-        },
-      };
-
-      const extractExceptionFrom = (ruleset: IRuleset, name: string, position: number): RulesetExceptionCollection => {
-        const exceptions = {};
-        const key = Object.keys(ruleset.exceptions)[position];
-        expect(ruleset.exceptions[key]).toEqual([name]);
-        exceptions[key] = ruleset.exceptions[key];
-
-        return exceptions;
-      };
-
-      it('should ignore specified rules violations in a referenced document', async () => {
-        spectral = new Spectral({ resolver: httpAndFileResolver });
-
-        const rules = {
-          'strings-maxLength': testRuleset.rules['strings-maxLength'],
-          schema: testRuleset.rules.schema,
-        };
-
-        spectral.setRuleset({ rules, exceptions: {}, functions: {} });
-
-        const first = await spectral.run(document, opts);
-
-        expect(first).toEqual([
-          expect.objectContaining({
-            code: 'strings-maxLength',
-          }),
-          expect.objectContaining({
-            code: 'schema',
-          }),
-        ]);
-
-        const exceptions = extractExceptionFrom(testRuleset, 'strings-maxLength', 0);
-
-        spectral.setRuleset({ rules, exceptions, functions: {} });
-
-        const second = await spectral.run(document, opts);
-
-        expect(second).toEqual([
-          expect.objectContaining({
-            code: 'schema',
-          }),
-        ]);
-      });
-
-      it('should ignore specified rules violations in "resolved=false" mode', async () => {
-        spectral = new Spectral({ resolver: httpAndFileResolver });
-
-        const rules = {
-          'no-yaml-remote-reference': testRuleset.rules['no-yaml-remote-reference'],
-          'no-remote-reference': testRuleset.rules['no-remote-reference'],
-          'no-json-schema-integer-type': testRuleset.rules['no-json-schema-integer-type'],
-          schema: testRuleset.rules.schema,
-        };
-
-        spectral.setRuleset({ rules, exceptions: {}, functions: {} });
-
-        const first = await spectral.run(document, opts);
-
-        expect(first).toEqual([
-          expect.objectContaining({
-            code: 'no-json-schema-integer-type',
-          }),
-          expect.objectContaining({
-            code: 'schema',
-          }),
-          expect.objectContaining({
-            code: 'no-remote-reference',
-          }),
-          expect.objectContaining({
-            code: 'no-yaml-remote-reference',
-          }),
-          expect.objectContaining({
-            code: 'no-json-schema-integer-type',
-          }),
-          expect.objectContaining({
-            code: 'no-remote-reference',
-          }),
-          expect.objectContaining({
-            code: 'no-yaml-remote-reference',
-          }),
-          expect.objectContaining({
-            code: 'no-json-schema-integer-type',
-          }),
-        ]);
-
-        spectral.setRuleset({ rules, exceptions: testRuleset.exceptions, functions: {} });
-
-        const second = await spectral.run(document, opts);
-
-        expect(second).toEqual([
-          expect.objectContaining({
-            code: 'schema',
-          }),
-          expect.objectContaining({
-            code: 'no-json-schema-integer-type',
-            path: ['components', 'schemas', 'integerTwo', 'type'],
-          }),
-        ]);
-      });
-    });
-  });
-
   test('should only run recommended rules, whether implicitly or explicitly', async () => {
     const target = {
       openapi: '3.0.2',
diff --git a/src/__tests__/linter.karma.test.ts b/src/__tests__/linter.karma.test.ts
index e02423b4..6ec47ef4 100644
--- a/src/__tests__/linter.karma.test.ts
+++ b/src/__tests__/linter.karma.test.ts
@@ -34,7 +34,6 @@ describe('Linter', () => {
       });
 
       spectral.setRuleset({
-        exceptions: {},
         functions: {
           fn: {
             source: null,
diff --git a/src/__tests__/linter.test.ts b/src/__tests__/linter.test.ts
index 0d1d2ef3..e715a225 100644
--- a/src/__tests__/linter.test.ts
+++ b/src/__tests__/linter.test.ts
@@ -182,7 +182,6 @@ describe('linter', () => {
         },
       },
       functions: {},
-      exceptions: {},
     });
 
     const result = await spectral.run(
@@ -852,7 +851,6 @@ responses:: !!foo
       spectral.setRuleset({
         rules: {},
         functions: {},
-        exceptions: {},
         parserOptions: {
           incompatibleValues: 'info',
         },
@@ -890,7 +888,6 @@ responses:: !!foo
       spectral.setRuleset({
         rules: {},
         functions: {},
-        exceptions: {},
         parserOptions: {
           incompatibleValues: 'off',
         },
@@ -914,7 +911,6 @@ responses:: !!foo
         spectral.setRuleset({
           rules: {},
           functions: {},
-          exceptions: {},
           parserOptions: {
             duplicateKeys: 'info',
           },
@@ -958,7 +954,6 @@ responses:: !!foo
         spectral.setRuleset({
           rules: {},
           functions: {},
-          exceptions: {},
           parserOptions: {
             duplicateKeys: 'off',
           },
diff --git a/src/__tests__/spectral.jest.test.ts b/src/__tests__/spectral.jest.test.ts
index 80d9abb1..bef96ca0 100644
--- a/src/__tests__/spectral.jest.test.ts
+++ b/src/__tests__/spectral.jest.test.ts
@@ -30,16 +30,6 @@ describe('Spectral', () => {
           severity: DiagnosticSeverity.Warning,
         }),
       });
-
-      Object.keys(s.exceptions).forEach(p => expect(path.isAbsolute(p)).toEqual(true));
-
-      expect(Object.entries(s.exceptions)).toEqual([
-        [expect.stringMatching('^/test/file.json#/info$'), ['info-contact', 'info-description']],
-        [expect.stringMatching('^/test/file.json#$'), ['oas3-api-servers']],
-        [expect.stringMatching('^/test/file.json#/paths/~1a.two/get$'), ['operation-success-response']],
-        [expect.stringMatching('^/test/file.json#/paths/~1b.three/get$'), ['operation-success-response']],
-        [expect.stringMatching('/__tests__/__fixtures__/rulesets/another.yaml#$'), ['dummy-rule', 'info-contact']],
-      ]);
     });
 
     test('should support loading rulesets over http', async () => {
diff --git a/src/__tests__/spectral.test.ts b/src/__tests__/spectral.test.ts
index 30a7ffdd..679ee083 100644
--- a/src/__tests__/spectral.test.ts
+++ b/src/__tests__/spectral.test.ts
@@ -1,13 +1,12 @@
 import { IGraphNodeData } from '@stoplight/json-ref-resolver/types';
 import { DiagnosticSeverity } from '@stoplight/types';
 import { DepGraph } from 'dependency-graph';
-import { escapeRegExp, merge } from 'lodash';
+import { merge } from 'lodash';
 
 import { Document } from '../document';
 import * as Parsers from '../parsers';
 import { RunRuleCollection, Spectral } from '../spectral';
 import { IResolver, RuleCollection } from '../types';
-import { RulesetExceptionCollection } from '../types/ruleset';
 
 const oasRuleset = JSON.parse(JSON.stringify(require('../rulesets/oas/index.json')));
 const asyncApiRuleset = JSON.parse(JSON.stringify(require('../rulesets/asyncapi/index.json')));
@@ -272,49 +271,4 @@ describe('spectral', () => {
       });
     });
   });
-
-  describe('setRuleset', () => {
-    const s = new Spectral();
-
-    describe('exceptions handling', () => {
-      it.each([['one.yaml#'], ['one.yaml#/'], ['one.yaml#/toto'], ['down/one.yaml#/toto'], ['../one.yaml#/toto']])(
-        'throws on relative locations  (location: "%s")',
-        location => {
-          const exceptions = { [location]: ['a'] };
-
-          expect(() => {
-            s.setRuleset({ rules: {}, functions: {}, exceptions });
-          }).toThrow(new RegExp(`.+\`${escapeRegExp(location)}\`.+is not a valid uri.+Only absolute Uris are allowed`));
-        },
-      );
-
-      it.each([
-        ['https://dot.com/one.yaml#/toto', 'https://dot.com/one.yaml#/toto'],
-        ['/local/one.yaml#/toto', '/local/one.yaml#/toto'],
-        ['c:/one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['c:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-      ])('normalizes absolute locations (location: "%s")', (location, expected) => {
-        const exceptions = { [location]: ['a'] };
-
-        s.setRuleset({ rules: {}, functions: {}, exceptions });
-
-        const locs = Object.keys(s.exceptions);
-        expect(locs).toEqual([expected]);
-      });
-
-      it('normalizes exceptions', () => {
-        const exceptions: RulesetExceptionCollection = {
-          '/test/file.yaml#/a': ['f', 'c', 'd', 'a'],
-          '/test/file.yaml#/b': ['1', '3', '3', '2'],
-        };
-
-        s.setRuleset({ rules: {}, functions: {}, exceptions });
-
-        expect(s.exceptions).toEqual({
-          '/test/file.yaml#/a': ['a', 'c', 'd', 'f'],
-          '/test/file.yaml#/b': ['1', '2', '3'],
-        });
-      });
-    });
-  });
 });
diff --git a/src/ruleset/__tests__/__fixtures__/exceptions/inheriting.yaml b/src/ruleset/__tests__/__fixtures__/exceptions/inheriting.yaml
deleted file mode 100644
index 25f408d3..00000000
--- a/src/ruleset/__tests__/__fixtures__/exceptions/inheriting.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-extends: ["./standalone.yaml"]
-except:
-  "four.yaml#": ["my-rule-4"]
diff --git a/src/ruleset/__tests__/__fixtures__/exceptions/invalid.yaml b/src/ruleset/__tests__/__fixtures__/exceptions/invalid.yaml
deleted file mode 100644
index 12be991c..00000000
--- a/src/ruleset/__tests__/__fixtures__/exceptions/invalid.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-rules: {}
-except:
-  "one.yaml##": ["my-rule"]
diff --git a/src/ruleset/__tests__/__fixtures__/exceptions/simple.yaml b/src/ruleset/__tests__/__fixtures__/exceptions/simple.yaml
deleted file mode 100644
index 02defd94..00000000
--- a/src/ruleset/__tests__/__fixtures__/exceptions/simple.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-rules: {}
-except:
-  "one.yaml#": ["my-rule-1"]
-  "../two.yaml#": ["my-rule-2"]
-  "sub/three.yaml#": ["my-rule-3"]
diff --git a/src/ruleset/__tests__/__fixtures__/exceptions/standalone.yaml b/src/ruleset/__tests__/__fixtures__/exceptions/standalone.yaml
deleted file mode 100644
index 02defd94..00000000
--- a/src/ruleset/__tests__/__fixtures__/exceptions/standalone.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-rules: {}
-except:
-  "one.yaml#": ["my-rule-1"]
-  "../two.yaml#": ["my-rule-2"]
-  "sub/three.yaml#": ["my-rule-3"]
diff --git a/src/ruleset/__tests__/reader.jest.test.ts b/src/ruleset/__tests__/reader.jest.test.ts
index c2f84614..5a3c428f 100644
--- a/src/ruleset/__tests__/reader.jest.test.ts
+++ b/src/ruleset/__tests__/reader.jest.test.ts
@@ -11,9 +11,6 @@ const invalidRuleset = path.join(__dirname, './__fixtures__/invalid-ruleset.json
 const fooRuleset = path.join(__dirname, './__fixtures__/foo-ruleset.json');
 const rulesetWithMissingFunctions = path.join(__dirname, './__fixtures__/ruleset-with-missing-functions.json');
 const fooExtendsBarRuleset = path.join(__dirname, './__fixtures__/foo-extends-bar-ruleset.json');
-const simpleExceptRuleset = path.join(__dirname, './__fixtures__/exceptions/simple.yaml');
-const inheritingExceptRuleset = path.join(__dirname, './__fixtures__/exceptions/inheriting.yaml');
-const invalidExceptRuleset = path.join(__dirname, './__fixtures__/exceptions/invalid.yaml');
 
 function getFixturePath(name: string): string {
   return path.join(__dirname, '__fixtures__', name);
@@ -212,7 +209,6 @@ describe('Rulesets reader', () => {
             source: 'https://unpkg.com/example-spectral-ruleset/functions/min.js',
           },
         },
-        exceptions: {},
       });
     });
 
@@ -270,7 +266,6 @@ describe('Rulesets reader', () => {
             source: 'https://unpkg.com/example-spectral-ruleset@0.0.3/functions/min.js',
           },
         },
-        exceptions: {},
       });
     });
   });
@@ -374,7 +369,6 @@ describe('Rulesets reader', () => {
 
   it('should handle ruleset with circular extensions', () => {
     return expect(readRuleset(fooExtendsBarRuleset)).resolves.toEqual({
-      exceptions: {},
       functions: {},
       rules: {
         'bar-rule': {
@@ -403,7 +397,6 @@ describe('Rulesets reader', () => {
 
   it('should handle ruleset that extends itself', () => {
     return expect(readRuleset(path.join(__dirname, './__fixtures__/self-extending-ruleset.json'))).resolves.toEqual({
-      exceptions: {},
       functions: {},
       rules: {
         'foo-rule': {
@@ -562,41 +555,4 @@ describe('Rulesets reader', () => {
       }),
     );
   });
-
-  describe('Exceptions loading', () => {
-    it('should handle loading a standalone ruleset', async () => {
-      const ruleset = await readRuleset(path.join(__dirname, './__fixtures__/exceptions/standalone.yaml'));
-
-      expect(Object.entries(ruleset.exceptions)).toEqual([
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/one.yaml#$'), ['my-rule-1']],
-        [expect.stringMatching('/__tests__/__fixtures__/two.yaml#$'), ['my-rule-2']],
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/sub/three.yaml#$'), ['my-rule-3']],
-      ]);
-    });
-
-    it('should throw when ruleset contains invalid exceptions', () => {
-      expect(readRuleset(invalidExceptRuleset)).rejects.toThrow('is not a valid uri');
-    });
-
-    it('should handle loading a ruleset deriving from a built-in one', async () => {
-      const ruleset = await readRuleset(simpleExceptRuleset);
-
-      expect(Object.entries(ruleset.exceptions)).toEqual([
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/one.yaml#$'), ['my-rule-1']],
-        [expect.stringMatching('/__tests__/__fixtures__/two.yaml#$'), ['my-rule-2']],
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/sub/three.yaml#$'), ['my-rule-3']],
-      ]);
-    });
-
-    it('should handle loading a ruleset deriving from another one', async () => {
-      const ruleset = await readRuleset(inheritingExceptRuleset);
-
-      expect(Object.entries(ruleset.exceptions)).toEqual([
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/one.yaml#$'), ['my-rule-1']],
-        [expect.stringMatching('/__tests__/__fixtures__/two.yaml#$'), ['my-rule-2']],
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/sub/three.yaml#$'), ['my-rule-3']],
-        [expect.stringMatching('/__tests__/__fixtures__/exceptions/four.yaml#$'), ['my-rule-4']],
-      ]);
-    });
-  });
 });
diff --git a/src/ruleset/__tests__/validation.test.ts b/src/ruleset/__tests__/validation.test.ts
index ce27bbad..74673755 100644
--- a/src/ruleset/__tests__/validation.test.ts
+++ b/src/ruleset/__tests__/validation.test.ts
@@ -225,24 +225,6 @@ Error at #/extends/0/1: allowed types are "off", "recommended" and "all"`),
     ).not.toThrow();
   });
 
-  describe('Exceptions validation', () => {
-    const rulesetsWithInvalidExceptStructures = [
-      { extends: ['foo'], except: '' },
-      { extends: ['foo'], except: { one: null } },
-      { extends: ['foo'], except: { one: [1] } },
-    ];
-
-    it.each(rulesetsWithInvalidExceptStructures)('throws when defined %p "except" do not match schema', ruleset => {
-      expect(() => {
-        assertValidRuleset(ruleset);
-      }).toThrow(
-        new RulesetValidationError(
-          'Error at #/except: must be a map where each key is either a path or a path+json-pointer or a json-pointer and the value is an array of rules',
-        ),
-      );
-    });
-  });
-
   describe('then validation', () => {
     describe('custom function', () => {
       it('given valid then, does not complain', () => {
diff --git a/src/ruleset/mergers/__tests__/exceptions.test.ts b/src/ruleset/mergers/__tests__/exceptions.test.ts
deleted file mode 100644
index df8cb678..00000000
--- a/src/ruleset/mergers/__tests__/exceptions.test.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { escapeRegExp } from 'lodash';
-import { RulesetExceptionCollection } from '../../../types/ruleset';
-import { mergeExceptions } from '../exceptions';
-
-describe('Ruleset exceptions merging', () => {
-  describe('when loaded from a ruleset', () => {
-    const dummyRulesetUri = './ruleset.yaml';
-
-    it('includes new exceptions', () => {
-      const target: RulesetExceptionCollection = {
-        'file.yaml#/a': [],
-        'file.yaml#/b': ['1', '2'],
-        'file.yaml': ['1', '2'],
-        '#/c': ['3', '4'],
-      };
-
-      const source: RulesetExceptionCollection = {
-        'file.yaml': ['6'],
-        'file.yaml#/c': ['3'],
-        'file.yaml#/d': ['4', '5'],
-        '#/c': ['7'],
-      };
-
-      mergeExceptions(target, source, dummyRulesetUri);
-
-      expect(target).toEqual({
-        '#/c': ['3', '4', '7'],
-        'file.yaml': ['1', '2', '6'],
-        'file.yaml#/a': [],
-        'file.yaml#/b': ['1', '2'],
-        'file.yaml#/c': ['3'],
-        'file.yaml#/d': ['4', '5'],
-      });
-    });
-
-    it('merges existing exceptions', () => {
-      const target: RulesetExceptionCollection = {
-        'file.yaml#/a': [],
-        'file.yaml#/b': ['1', '3'],
-      };
-
-      const source: RulesetExceptionCollection = {
-        'file.yaml#/a': ['0'],
-        'file.yaml#/b': ['2', '4'],
-      };
-
-      mergeExceptions(target, source, dummyRulesetUri);
-
-      expect(target).toEqual({
-        'file.yaml#/a': ['0'],
-        'file.yaml#/b': ['1', '2', '3', '4'],
-      });
-    });
-
-    it('deduplicates exceptions', () => {
-      const target: RulesetExceptionCollection = {
-        'file.yaml#/a': [],
-        'file.yaml#/b': ['1', '3'],
-      };
-
-      const source: RulesetExceptionCollection = {
-        'file.yaml#/a': ['0', '0'],
-        'file.yaml#/b': ['2', '4', '2', '3'],
-      };
-
-      mergeExceptions(target, source, dummyRulesetUri);
-
-      expect(target).toEqual({
-        'file.yaml#/a': ['0'],
-        'file.yaml#/b': ['1', '2', '3', '4'],
-      });
-    });
-
-    describe('Validation', () => {
-      describe('Invalid locations', () => {
-        const invalidLocations = ['##where', '#where', '../123.yaml#where'];
-
-        it.each(invalidLocations)(
-          'throws when locations are not valid uris (including fragment): "%s"',
-          async location => {
-            const source = { [location]: ['a'] };
-
-            expect(() => {
-              mergeExceptions({}, source, dummyRulesetUri);
-            }).toThrow(
-              new RegExp(
-                `.+\`${escapeRegExp(dummyRulesetUri)}\`.+\\(key \`${escapeRegExp(location)}\`.+is not a valid uri`,
-              ),
-            );
-          },
-        );
-      });
-
-      describe('throws on empty rules array', () => {
-        const location = 'one.yaml#/';
-        const source = { [location]: [] };
-
-        expect(() => {
-          mergeExceptions({}, source, dummyRulesetUri);
-        }).toThrow(
-          new RegExp(
-            `.+\`${escapeRegExp(dummyRulesetUri)}\`.+\\(key \`${escapeRegExp(
-              location,
-            )}\`.+An empty array of rules has been provided`,
-          ),
-        );
-      });
-
-      describe('throws on empty rule name', () => {
-        const location = 'one.yaml#/';
-        const source = { [location]: ['b', ''] };
-
-        expect(() => {
-          mergeExceptions({}, source, dummyRulesetUri);
-        }).toThrow(
-          new RegExp(
-            `.+\`${escapeRegExp(dummyRulesetUri)}\`.+\\(key \`${escapeRegExp(
-              location,
-            )}\`.+A rule with an empty name has been provided`,
-          ),
-        );
-      });
-    });
-
-    describe('Normalization', () => {
-      const relativeLocations: Array<[string, string, string]> = [
-        ['./ruleset.yaml', '#/toto', '#/toto'],
-        ['./ruleset.yaml', 'one.yaml#', 'one.yaml#'],
-        ['./ruleset.yaml', 'one.yaml#/', 'one.yaml#/'],
-        ['./ruleset.yaml', 'one.yaml', 'one.yaml'],
-        ['./ruleset.yaml', 'one.yaml#/toto', 'one.yaml#/toto'],
-        ['./ruleset.yaml', 'down/one.yaml#/toto', 'down/one.yaml#/toto'],
-        ['./ruleset.yaml', '../one.yaml#/toto', '../one.yaml#/toto'],
-        ['../ruleset.yaml', 'one.yaml#', '../one.yaml#'],
-        ['../ruleset.yaml', 'one.yaml#/', '../one.yaml#/'],
-        ['../ruleset.yaml', 'one.yaml#/toto', '../one.yaml#/toto'],
-        ['../ruleset.yaml', 'down/one.yaml#/toto', '../down/one.yaml#/toto'],
-        ['../ruleset.yaml', '../one.yaml#/toto', '../../one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'one.yaml#', 'https://dot.com/r/one.yaml#'],
-        ['https://dot.com/r/ruleset.yaml', 'one.yaml#/', 'https://dot.com/r/one.yaml#/'],
-        ['https://dot.com/r/ruleset.yaml', 'one.yaml#/toto', 'https://dot.com/r/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'down/one.yaml#/toto', 'https://dot.com/r/down/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', '../one.yaml#/toto', 'https://dot.com/one.yaml#/toto'],
-      ];
-
-      it.each(relativeLocations)(
-        'combines relative locations with ruleset uri (ruleset: "%s", location: "%s")',
-        (rulesetUri, location, expectedLocation) => {
-          const source = { [location]: ['a'] };
-          const target = {};
-
-          mergeExceptions(target, source, rulesetUri);
-
-          const expected = { [expectedLocation]: ['a'] };
-          expect(target).toEqual(expected);
-        },
-      );
-
-      const absoluteLocations: Array<[string, string, string]> = [
-        ['./ruleset.yaml', 'https://dot.com/one.yaml#/toto', 'https://dot.com/one.yaml#/toto'],
-        ['../ruleset.yaml', 'https://dot.com/one.yaml#/toto', 'https://dot.com/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'https://dot.com/one.yaml#/toto', 'https://dot.com/one.yaml#/toto'],
-        ['./ruleset.yaml', '/local/one.yaml#/toto', '/local/one.yaml#/toto'],
-        ['../ruleset.yaml', '/local/one.yaml#/toto', '/local/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', '/local/one.yaml#/toto', '/local/one.yaml#/toto'],
-        ['./ruleset.yaml', 'c:/one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['../ruleset.yaml', 'c:/one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'c:/one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['./ruleset.yaml', 'c:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['../ruleset.yaml', 'c:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'c:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['./ruleset.yaml', 'C:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['../ruleset.yaml', 'C:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-        ['https://dot.com/r/ruleset.yaml', 'C:\\one.yaml#/toto', 'c:/one.yaml#/toto'],
-      ];
-
-      it.each(absoluteLocations)(
-        'normalize absolute locations (ruleset: "%s", location: "%s")',
-        (rulesetUri, location, expectedLocation) => {
-          const source = { [location]: ['a'] };
-          const target = {};
-
-          mergeExceptions(target, source, rulesetUri);
-
-          const expected = { [expectedLocation]: ['a'] };
-          expect(target).toEqual(expected);
-        },
-      );
-    });
-  });
-});
diff --git a/src/runner/utils/__tests__/pivotExceptions.test.ts b/src/runner/utils/__tests__/pivotExceptions.test.ts
deleted file mode 100644
index 11d5d9c6..00000000
--- a/src/runner/utils/__tests__/pivotExceptions.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { DiagnosticSeverity, Dictionary } from '@stoplight/types';
-import { ExceptionLocation, pivotExceptions } from '../pivotExceptions';
-
-import { Rule } from '../../../rule';
-import { InvalidUriError } from '../../../ruleset/mergers/exceptions';
-
-describe('pivotExceptions', () => {
-  let dummyRule: Rule;
-
-  beforeEach(() => {
-    dummyRule = new Rule('', {
-      severity: DiagnosticSeverity.Error,
-      given: '',
-      then: {
-        function: 'truthy',
-      },
-    });
-  });
-
-  it('ignores exceptions for rules that are not part of the run', () => {
-    const exceptions = {
-      'one#/1': ['a', 'c'],
-    };
-
-    const runRules = { a: dummyRule };
-    const expected: Dictionary<ExceptionLocation[], string> = {
-      a: [{ source: 'one', path: ['1'] }],
-    };
-
-    expect(pivotExceptions(exceptions, runRules)).toEqual(expected);
-  });
-
-  it('returns a rule based dictionary', () => {
-    const exceptions = {
-      'one#/1': ['a', 'c'],
-      'two#/2': ['b', 'd'],
-      'three#/3': ['b', 'a'],
-    };
-
-    const runRules = { a: dummyRule, b: dummyRule, c: dummyRule, d: dummyRule };
-    const expected: Dictionary<ExceptionLocation[], string> = {
-      a: [
-        { source: 'one', path: ['1'] },
-        { source: 'three', path: ['3'] },
-      ],
-      b: [
-        { source: 'two', path: ['2'] },
-        { source: 'three', path: ['3'] },
-      ],
-      c: [{ source: 'one', path: ['1'] }],
-      d: [{ source: 'two', path: ['2'] }],
-    };
-
-    expect(pivotExceptions(exceptions, runRules)).toEqual(expected);
-  });
-
-  it.each([['']])('throws upon detected malformed location (%s)', malformed => {
-    const bad = { [malformed]: ['a'] };
-
-    expect(() => {
-      pivotExceptions(bad, {});
-    }).toThrow(InvalidUriError);
-  });
-});
diff --git a/test-harness/scenarios/exceptions.circular.reference.oas3.scenario b/test-harness/scenarios/exceptions.circular.reference.oas3.scenario
deleted file mode 100644
index 6d97e65b..00000000
--- a/test-harness/scenarios/exceptions.circular.reference.oas3.scenario
+++ /dev/null
@@ -1,87 +0,0 @@
-====test====
-Can ignore circular refs results (reference scenario)
-====document====
-openapi: 3.0.2
-info:
-  title: Circular circurlarity
-  description: This is a description
-  version: draft
-  contact:
-    email: me@me.com
-paths:
-  /self_referencing_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-  /cyclic_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-  /looping_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-components:
-  schemas:
-    links:
-      type: object
-      properties:
-        id:
-          type: string
-        references:
-          type: array
-          items:
-            $ref: '#/components/schemas/links'
-    descriptor:
-      type: object
-      properties:
-        id:
-          type: string
-        links:
-          $ref: '#/components/schemas/links'
-      example:
-        processes:
-          - id: 17
-            summary: Enhanced Index
-====asset:ruleset====
-extends: spectral:oas
-====command====
-{bin} lint {document} -r {asset:ruleset}
-====stdout====
-OpenAPI 3.x detected
-OpenAPI 3.0.x detected
-
-{document}
-  1:1  warning  oas3-api-servers       OpenAPI `servers` must be present and non-empty array.
- 10:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./self_referencing_graphs.get
- 10:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./self_referencing_graphs.get
- 22:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./cyclic_graphs.get
- 22:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./cyclic_graphs.get
- 34:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./looping_graphs.get
- 34:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./looping_graphs.get
-
-✖ 7 problems (0 errors, 7 warnings, 0 infos, 0 hints)
diff --git a/test-harness/scenarios/exceptions.circular.silenced.oas3.scenario b/test-harness/scenarios/exceptions.circular.silenced.oas3.scenario
deleted file mode 100644
index acea9a2c..00000000
--- a/test-harness/scenarios/exceptions.circular.silenced.oas3.scenario
+++ /dev/null
@@ -1,98 +0,0 @@
-====test====
-Can ignore circular refs results
-====document====
-openapi: 3.0.2
-info:
-  title: Circular circurlarity
-  description: This is a description
-  version: draft
-  contact:
-    email: me@me.com
-paths:
-  /self_referencing_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-  /cyclic_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-  /looping_graphs:
-    get:
-      description: A good one.
-      responses:
-        '200':
-          description: Hello
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/descriptor'
-              example:
-                A weird example.
-components:
-  schemas:
-    links:
-      type: object
-      properties:
-        id:
-          type: string
-        references:
-          type: array
-          items:
-            $ref: '#/components/schemas/links'
-    descriptor:
-      type: object
-      properties:
-        id:
-          type: string
-        links:
-          $ref: '#/components/schemas/links'
-      example:
-        processes:
-          - id: 17
-            summary: Enhanced Index
-====asset:ruleset====
-extends: spectral:oas
-
-except:
-  "{document}#/components/schemas/descriptor/example":
-    - oas3-valid-schema-example
-    - oas3-valid-content-schema-example
-  "{document}#/paths/~1looping_graphs/get/responses/200/content/application~1json/example":
-    - oas3-valid-oas-content-example
-  "{document}#/paths/~1cyclic_graphs/get/responses/200/content/application~1json/example":
-    - oas3-valid-oas-content-example
-  "{document}#/paths/~1self_referencing_graphs/get/responses/200/content/application~1json/example":
-    - oas3-valid-oas-content-example
-====command====
-{bin} lint {document} -r {asset:ruleset}
-====stdout====
-OpenAPI 3.x detected
-OpenAPI 3.0.x detected
-
-{document}
-  1:1  warning  oas3-api-servers       OpenAPI `servers` must be present and non-empty array.
- 10:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./self_referencing_graphs.get
- 10:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./self_referencing_graphs.get
- 22:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./cyclic_graphs.get
- 22:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./cyclic_graphs.get
- 34:9  warning  operation-operationId  Operation should have an `operationId`.                 paths./looping_graphs.get
- 34:9  warning  operation-tags         Operation should have non-empty `tags` array.           paths./looping_graphs.get
-
-✖ 7 problems (0 errors, 7 warnings, 0 infos, 0 hints)
diff --git a/test-harness/scenarios/exceptions.oas3.scenario b/test-harness/scenarios/exceptions.oas3.scenario
deleted file mode 100644
index 7125e602..00000000
--- a/test-harness/scenarios/exceptions.oas3.scenario
+++ /dev/null
@@ -1,26 +0,0 @@
-====test====
-Do not report exceptions
-====document====
-openapi: 3.0.0
-info:
-  version: 1.0.0
-  title: Stoplight
-paths: {}
-====asset:ruleset====
-extends: spectral:oas
-
-except:
-  "{document}#":
-    - oas3-api-servers
-  "{document}#/info":
-    - info-contact
-====command====
-{bin} lint {document} -r {asset:ruleset}
-====stdout====
-OpenAPI 3.x detected
-OpenAPI 3.0.x detected
-
-{document}
- 2:6  warning  info-description  OpenAPI object info `description` must be present and non-empty string.  info
-
-✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints)
diff --git a/test-harness/scenarios/exceptions.oas3.stdin.scenario b/test-harness/scenarios/exceptions.oas3.stdin.scenario
deleted file mode 100644
index dbceb3a6..00000000
--- a/test-harness/scenarios/exceptions.oas3.stdin.scenario
+++ /dev/null
@@ -1,29 +0,0 @@
-====test====
-Do not report exceptions when document comes through stdin
-====document====
-openapi: 3.0.0
-info:
-  version: 1.0.0
-  title: Stoplight
-paths: {}
-====asset:ruleset====
-extends: spectral:oas
-
-except:
-  "{document}#":
-    - oas3-api-servers
-  "{document}#/info":
-    - info-contact
-====command====
-cat {document} | {bin} lint -r {asset:ruleset}
-====stdout====
-OpenAPI 3.x detected
-OpenAPI 3.0.x detected
-
-<STDIN>
- 1:1  warning  except-but-stdin  The ruleset contains `except` entries. However, they cannot be enforced when the input is passed through stdin.
- 1:1  warning  oas3-api-servers  OpenAPI `servers` must be present and non-empty array.
- 2:6  warning  info-contact      Info object should contain `contact` object.                                                                     info
- 2:6  warning  info-description  OpenAPI object info `description` must be present and non-empty string.                                          info
-
-✖ 4 problems (0 errors, 4 warnings, 0 infos, 0 hints)