Repository: stoplightio/spectral
Pull request #2343 (2022-11-19 12:33:09)

fix(core): more accurate ruleset error paths 
Related to https://github.com/stoplightio/spectral/issues/2338

**Checklist**

- [x] Tests added / updated
- [ ] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No





Patch:
diff --git a/packages/core/src/ruleset/function.ts b/packages/core/src/ruleset/function.ts
index b187fc13..8360ba90 100644
--- a/packages/core/src/ruleset/function.ts
+++ b/packages/core/src/ruleset/function.ts
@@ -22,10 +22,27 @@ export class RulesetFunctionValidationError extends RulesetValidationError {
     super(
       'invalid-function-options',
       RulesetFunctionValidationError.printMessage(fn, error),
-      error.instancePath.slice(1).split('/'),
+      RulesetFunctionValidationError.getPath(error),
     );
   }
 
+  private static getPath(error: ErrorObject): string[] {
+    const path: string[] = [
+      'functionOptions',
+      ...(error.instancePath === '' ? [] : error.instancePath.slice(1).split('/')),
+    ];
+
+    switch (error.keyword) {
+      case 'additionalProperties': {
+        const additionalProperty = (error as AdditionalPropertiesError).params.additionalProperty;
+        path.push(additionalProperty);
+        break;
+      }
+    }
+
+    return path;
+  }
+
   private static printMessage(fn: string, error: ErrorObject): string {
     switch (error.keyword) {
       case 'type': {
@@ -157,7 +174,7 @@ export function createRulesetFunction<I, O>(
       throw new RulesetValidationError(
         'invalid-function-options',
         `"${fn.name || '<unknown>'}" function does not accept any options`,
-        [],
+        ['functionOptions'],
       );
     } else if (
       'errors' in validateOptions &&
@@ -171,7 +188,7 @@ export function createRulesetFunction<I, O>(
       throw new RulesetValidationError(
         'invalid-function-options',
         `"functionOptions" of "${fn.name || '<unknown>'}" function must be valid`,
-        [],
+        ['functionOptions'],
       );
     }
   };
diff --git a/packages/core/src/ruleset/ruleset.ts b/packages/core/src/ruleset/ruleset.ts
index 56095748..8cc01a72 100644
--- a/packages/core/src/ruleset/ruleset.ts
+++ b/packages/core/src/ruleset/ruleset.ts
@@ -63,10 +63,10 @@ export class Ruleset {
     if (isPlainObject(maybeDefinition) && 'extends' in maybeDefinition) {
       const { extends: _, ...def } = maybeDefinition;
       // we don't want to validate extends - this is going to happen later on (line 29)
-      assertValidRuleset({ extends: [], ...def });
+      assertValidRuleset({ extends: [], ...def }, 'js');
       definition = maybeDefinition as RulesetDefinition;
     } else {
-      assertValidRuleset(maybeDefinition);
+      assertValidRuleset(maybeDefinition, 'js');
       definition = maybeDefinition;
     }
 
diff --git a/packages/core/src/ruleset/validation/errors.ts b/packages/core/src/ruleset/validation/errors.ts
index af866078..3c92aa7d 100644
--- a/packages/core/src/ruleset/validation/errors.ts
+++ b/packages/core/src/ruleset/validation/errors.ts
@@ -18,7 +18,7 @@ export type RulesetValidationErrorCode =
   | 'undefined-alias';
 
 interface IRulesetValidationSingleError extends Pick<IDiagnostic, 'message' | 'path'> {
-  code: RulesetValidationErrorCode;
+  readonly code: RulesetValidationErrorCode;
 }
 
 export class RulesetValidationError extends Error implements IRulesetValidationSingleError {
diff --git a/packages/core/src/ruleset/validation/validators/function.ts b/packages/core/src/ruleset/validation/validators/function.ts
index 83442522..bf6249eb 100644
--- a/packages/core/src/ruleset/validation/validators/function.ts
+++ b/packages/core/src/ruleset/validation/validators/function.ts
@@ -24,7 +24,7 @@ export function validateFunction(
     validator(opts);
   } catch (ex) {
     if (ex instanceof ReferenceError) {
-      return new RulesetValidationError('undefined-function', ex.message, toParsedPath(path));
+      return new RulesetValidationError('undefined-function', ex.message, [...toParsedPath(path), 'function']);
     }
 
     return wrapError(ex, path);
diff --git a/tsconfig.json b/tsconfig.json
index 858e0c86..8f7094ce 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,6 +4,8 @@
     "baseUrl": ".",
     "paths": {
       "@stoplight/spectral-core": ["packages/core/src/index.ts"],
+      "@stoplight/spectral-core/ruleset": ["packages/core/src/ruleset/index.ts"],
+      "@stoplight/spectral-core/ruleset/validation": ["packages/core/src/ruleset/validation/index.ts"],
       "@stoplight/spectral-formats": ["packages/formats/src/index.ts"],
       "@stoplight/spectral-functions": ["packages/functions/src/index.ts"],
       "@stoplight/spectral-parsers": ["packages/parsers/src/index.ts"],


Test patch:
diff --git a/packages/core/src/ruleset/validation/__tests__/validation.test.ts b/packages/core/src/ruleset/validation/__tests__/validation.test.ts
index 3cfab69b..59a88f91 100644
--- a/packages/core/src/ruleset/validation/__tests__/validation.test.ts
+++ b/packages/core/src/ruleset/validation/__tests__/validation.test.ts
@@ -773,7 +773,12 @@ describe('JS Ruleset Validation', () => {
         }),
       ).toThrowAggregateError(
         new AggregateError([
-          new RulesetValidationError('undefined-function', 'Function is not defined', ['rules', 'rule', 'then']),
+          new RulesetValidationError('undefined-function', 'Function is not defined', [
+            'rules',
+            'rule',
+            'then',
+            'function',
+          ]),
         ]),
       );
     });
diff --git a/packages/functions/src/__tests__/__helpers__/tester.ts b/packages/functions/src/__tests__/__helpers__/tester.ts
index fe135562..bc77112c 100644
--- a/packages/functions/src/__tests__/__helpers__/tester.ts
+++ b/packages/functions/src/__tests__/__helpers__/tester.ts
@@ -6,15 +6,8 @@ import {
   IRuleResult,
   RulesetFunction,
   RulesetFunctionWithValidator,
-  RulesetValidationError,
 } from '@stoplight/spectral-core';
 
-import { isError } from 'lodash';
-
-function isAggregateError(maybeAggregateError: unknown): maybeAggregateError is Error & { errors: unknown[] } {
-  return isError(maybeAggregateError) && maybeAggregateError.constructor.name === 'AggregateError';
-}
-
 export default async function <O = unknown>(
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   fn: RulesetFunction<any, any> | RulesetFunctionWithValidator<any, any>,
@@ -23,31 +16,19 @@ export default async function <O = unknown>(
   rule?: Partial<Omit<RuleDefinition, 'then'>> & { then?: Partial<RuleDefinition['then']> },
 ): Promise<Pick<IRuleResult, 'path' | 'message'>[]> {
   const s = new Spectral();
-  try {
-    s.setRuleset({
-      rules: {
-        'my-rule': {
-          given: '$',
-          ...rule,
-          then: {
-            ...(rule?.then as Ruleset['rules']['then']),
-            function: fn,
-            functionOptions: opts,
-          },
+  s.setRuleset({
+    rules: {
+      'my-rule': {
+        given: '$',
+        ...rule,
+        then: {
+          ...(rule?.then as Ruleset['rules']['then']),
+          function: fn,
+          functionOptions: opts,
         },
       },
-    });
-  } catch (ex) {
-    if (isAggregateError(ex)) {
-      for (const e of ex.errors) {
-        if (e instanceof RulesetValidationError) {
-          e.path.length = 0;
-        }
-      }
-    }
-
-    throw ex;
-  }
+    },
+  });
 
   const results = await s.run(input instanceof Document ? input : JSON.stringify(input));
   return results
diff --git a/packages/functions/src/__tests__/alphabetical.test.ts b/packages/functions/src/__tests__/alphabetical.test.ts
index fe6c2007..d8ba91d8 100644
--- a/packages/functions/src/__tests__/alphabetical.test.ts
+++ b/packages/functions/src/__tests__/alphabetical.test.ts
@@ -132,17 +132,33 @@ describe('Core Functions / Alphabetical', () => {
       expect(await runAlphabetical([], opts)).toEqual([]);
     });
 
-    it.each<[unknown, string]>([
-      [{ foo: true }, '"alphabetical" function does not support "foo" option'],
+    it.each<[unknown, RulesetValidationError]>([
+      [
+        { foo: true },
+        new RulesetValidationError(
+          'invalid-function-options',
+          '"alphabetical" function does not support "foo" option',
+          ['rules', 'my-rule', 'then', 'functionOptions', 'foo'],
+        ),
+      ],
       [
         2,
-        '"alphabetical" function has invalid options specified. Example valid options: null (no options), { "keyedBy": "my-key" }',
+        new RulesetValidationError(
+          'invalid-function-options',
+          '"alphabetical" function has invalid options specified. Example valid options: null (no options), { "keyedBy": "my-key" }',
+          ['rules', 'my-rule', 'then', 'functionOptions'],
+        ),
+      ],
+      [
+        { keyedBy: 2 },
+        new RulesetValidationError(
+          'invalid-function-options',
+          '"alphabetical" function and its "keyedBy" option accepts only the following types: string',
+          ['rules', 'my-rule', 'then', 'functionOptions', 'keyedBy'],
+        ),
       ],
-      [{ keyedBy: 2 }, '"alphabetical" function and its "keyedBy" option accepts only the following types: string'],
     ])('given invalid %p options, should throw', async (opts, error) => {
-      await expect(runAlphabetical([], opts)).rejects.toThrowAggregateError(
-        new AggregateError([new RulesetValidationError('invalid-function-options', error, [])]),
-      );
+      await expect(runAlphabetical([], opts)).rejects.toThrowAggregateError(new AggregateError([error]));
     });
   });
 });
diff --git a/packages/functions/src/__tests__/casing.test.ts b/packages/functions/src/__tests__/casing.test.ts
index b7860526..b0c2e03a 100644
--- a/packages/functions/src/__tests__/casing.test.ts
+++ b/packages/functions/src/__tests__/casing.test.ts
@@ -381,13 +381,21 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function and its "type" option accept the following values: flat, camel, pascal, kebab, cobol, snake, macro',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'type'],
           ),
         ],
       ],
       [
         { type: 'macro', foo: true },
-        [new RulesetValidationError('invalid-function-options', '"casing" function does not support "foo" option', [])],
+        [
+          new RulesetValidationError('invalid-function-options', '"casing" function does not support "foo" option', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+            'foo',
+          ]),
+        ],
       ],
       [
         {
@@ -399,7 +407,7 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function is missing "separator.char" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'separator'],
           ),
         ],
       ],
@@ -413,7 +421,7 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function is missing "separator.char" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'separator'],
           ),
         ],
       ],
@@ -423,7 +431,7 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function does not support "separator.foo" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'foo'],
           ),
         ],
       ],
@@ -438,7 +446,7 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function and its "separator.char" option accepts only char, i.e. "I" or "/"',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'char'],
           ),
         ],
       ],
@@ -453,7 +461,7 @@ describe('Core Functions / Casing', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"casing" function and its "separator.char" option accepts only char, i.e. "I" or "/"',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'separator', 'char'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/defined.test.ts b/packages/functions/src/__tests__/defined.test.ts
index 05d3e54b..56ebc51a 100644
--- a/packages/functions/src/__tests__/defined.test.ts
+++ b/packages/functions/src/__tests__/defined.test.ts
@@ -25,7 +25,12 @@ describe('Core Functions / Defined', () => {
     it.each([{}, 2])('given invalid %p options, should throw', async opts => {
       await expect(runDefined([], opts)).rejects.toThrowAggregateError(
         new AggregateError([
-          new RulesetValidationError('invalid-function-options', '"defined" function does not accept any options', []),
+          new RulesetValidationError('invalid-function-options', '"defined" function does not accept any options', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+          ]),
         ]),
       );
     });
diff --git a/packages/functions/src/__tests__/enumeration.test.ts b/packages/functions/src/__tests__/enumeration.test.ts
index ab92c140..019cd661 100644
--- a/packages/functions/src/__tests__/enumeration.test.ts
+++ b/packages/functions/src/__tests__/enumeration.test.ts
@@ -44,7 +44,7 @@ describe('Core Functions / Enumeration', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"enumeration" function does not support "foo" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'foo'],
           ),
         ],
       ],
@@ -56,7 +56,7 @@ describe('Core Functions / Enumeration', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"enumeration" and its "values" option support only arrays of primitive values, i.e. ["Berlin", "London", "Paris"]',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'values'],
           ),
         ],
       ],
@@ -66,7 +66,7 @@ describe('Core Functions / Enumeration', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"enumeration" function has invalid options specified. Example valid options: { "values": ["Berlin", "London", "Paris"] }, { "values": [2, 3, 5, 8, 13, 21] }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -76,7 +76,7 @@ describe('Core Functions / Enumeration', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"enumeration" function has invalid options specified. Example valid options: { "values": ["Berlin", "London", "Paris"] }, { "values": [2, 3, 5, 8, 13, 21] }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/falsy.test.ts b/packages/functions/src/__tests__/falsy.test.ts
index 7799b442..a63c9878 100644
--- a/packages/functions/src/__tests__/falsy.test.ts
+++ b/packages/functions/src/__tests__/falsy.test.ts
@@ -25,7 +25,12 @@ describe('Core Functions / Falsy', () => {
     it.each([{}, 2])('given invalid %p options, should throw', async opts => {
       await expect(runFalsy([], opts)).rejects.toThrowAggregateError(
         new AggregateError([
-          new RulesetValidationError('invalid-function-options', '"falsy" function does not accept any options', []),
+          new RulesetValidationError('invalid-function-options', '"falsy" function does not accept any options', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+          ]),
         ]),
       );
     });
diff --git a/packages/functions/src/__tests__/length.test.ts b/packages/functions/src/__tests__/length.test.ts
index 9e3fff23..4ab9cb38 100644
--- a/packages/functions/src/__tests__/length.test.ts
+++ b/packages/functions/src/__tests__/length.test.ts
@@ -61,7 +61,7 @@ describe('Core Functions / Length', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"length" function has invalid options specified. Example valid options: { "min": 2 }, { "max": 5 }, { "min": 0, "max": 10 }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -71,7 +71,7 @@ describe('Core Functions / Length', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"length" function has invalid options specified. Example valid options: { "min": 2 }, { "max": 5 }, { "min": 0, "max": 10 }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -80,15 +80,23 @@ describe('Core Functions / Length', () => {
           min: 2,
           foo: true,
         },
-        [new RulesetValidationError('invalid-function-options', '"length" function does not support "foo" option', [])],
+        [
+          new RulesetValidationError('invalid-function-options', '"length" function does not support "foo" option', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+            'foo',
+          ]),
+        ],
       ],
       [
         { min: '2' },
         [
           new RulesetValidationError(
             'invalid-function-options',
-            '"length" function and its "min" option accepts only the following types: number',
-            [],
+            `"length" function and its "min" option accepts only the following types: number`,
+            ['rules', 'my-rule', 'then', 'functionOptions', 'min'],
           ),
         ],
       ],
@@ -99,7 +107,7 @@ describe('Core Functions / Length', () => {
           new RulesetValidationError(
             'invalid-function-options',
             `"length" function and its "max" option accepts only the following types: number`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'max'],
           ),
         ],
       ],
@@ -109,12 +117,12 @@ describe('Core Functions / Length', () => {
           new RulesetValidationError(
             'invalid-function-options',
             `"length" function and its "min" option accepts only the following types: number`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'min'],
           ),
           new RulesetValidationError(
             'invalid-function-options',
             `"length" function and its "max" option accepts only the following types: number`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'max'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/pattern.test.ts b/packages/functions/src/__tests__/pattern.test.ts
index 74b98e08..9cc030cc 100644
--- a/packages/functions/src/__tests__/pattern.test.ts
+++ b/packages/functions/src/__tests__/pattern.test.ts
@@ -59,7 +59,7 @@ describe('Core Functions / Pattern', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"pattern" function has invalid options specified. Example valid options: { "match": "^Stoplight" }, { "notMatch": "Swagger" }, { "match": "Stoplight", "notMatch": "Swagger" }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -69,18 +69,20 @@ describe('Core Functions / Pattern', () => {
           new RulesetValidationError(
             'invalid-function-options',
             `"pattern" function has invalid options specified. Example valid options: { "match": "^Stoplight" }, { "notMatch": "Swagger" }, { "match": "Stoplight", "notMatch": "Swagger" }`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
       [
         { foo: true },
         [
-          new RulesetValidationError(
-            'invalid-function-options',
-            '"pattern" function does not support "foo" option',
-            [],
-          ),
+          new RulesetValidationError('invalid-function-options', '"pattern" function does not support "foo" option', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+            'foo',
+          ]),
         ],
       ],
       [
@@ -89,7 +91,7 @@ describe('Core Functions / Pattern', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"pattern" function and its "match" option must be string or RegExp instance',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'match'],
           ),
         ],
       ],
@@ -99,7 +101,7 @@ describe('Core Functions / Pattern', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"pattern" function and its "notMatch" option must be string or RegExp instance',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'notMatch'],
           ),
         ],
       ],
@@ -109,12 +111,12 @@ describe('Core Functions / Pattern', () => {
           new RulesetValidationError(
             'invalid-function-options',
             `"pattern" function and its "match" option must be string or RegExp instance`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'match'],
           ),
           new RulesetValidationError(
             'invalid-function-options',
             `"pattern" function and its "notMatch" option must be string or RegExp instance`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'notMatch'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/schema.test.ts b/packages/functions/src/__tests__/schema.test.ts
index ee7b5395..ec45b5fd 100644
--- a/packages/functions/src/__tests__/schema.test.ts
+++ b/packages/functions/src/__tests__/schema.test.ts
@@ -485,13 +485,21 @@ describe('Core Functions / Schema', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"schema" function has invalid options specified. Example valid options: { "schema": { /* any JSON Schema can be defined here */ } , { "schema": { "type": "object" }, "dialect": "auto" }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
       [
         { schema: { type: 'object' }, foo: true },
-        [new RulesetValidationError('invalid-function-options', '"schema" function does not support "foo" option', [])],
+        [
+          new RulesetValidationError('invalid-function-options', '"schema" function does not support "foo" option', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+            'foo',
+          ]),
+        ],
       ],
       [
         { schema: { type: 'object' }, oasVersion: 1 },
@@ -499,7 +507,7 @@ describe('Core Functions / Schema', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"schema" function does not support "oasVersion" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'oasVersion'],
           ),
         ],
       ],
@@ -509,7 +517,7 @@ describe('Core Functions / Schema', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"schema" function and its "dialect" option accepts only the following values: "auto", "draft4", "draft6", "draft7", "draft2019-09", "draft2020-12"',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'dialect'],
           ),
         ],
       ],
@@ -519,7 +527,7 @@ describe('Core Functions / Schema', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"schema" function and its "allErrors" option accepts only the following types: boolean',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'allErrors'],
           ),
         ],
       ],
@@ -529,12 +537,12 @@ describe('Core Functions / Schema', () => {
           new RulesetValidationError(
             'invalid-function-options',
             `"schema" function and its "schema" option accepts only the following types: object`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'schema'],
           ),
           new RulesetValidationError(
             'invalid-function-options',
             `"schema" function and its "allErrors" option accepts only the following types: boolean`,
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'allErrors'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/truthy.test.ts b/packages/functions/src/__tests__/truthy.test.ts
index b135382a..32da4fb4 100644
--- a/packages/functions/src/__tests__/truthy.test.ts
+++ b/packages/functions/src/__tests__/truthy.test.ts
@@ -25,7 +25,12 @@ describe('Core Functions / Truthy', () => {
     it.each([{}, 2])('given invalid %p options, should throw', async opts => {
       await expect(runTruthy([], opts)).rejects.toThrowAggregateError(
         new AggregateError([
-          new RulesetValidationError('invalid-function-options', '"truthy" function does not accept any options', []),
+          new RulesetValidationError('invalid-function-options', '"truthy" function does not accept any options', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+          ]),
         ]),
       );
     });
diff --git a/packages/functions/src/__tests__/unreferencedReusableObject.test.ts b/packages/functions/src/__tests__/unreferencedReusableObject.test.ts
index 7fdcbe80..3f1deb95 100644
--- a/packages/functions/src/__tests__/unreferencedReusableObject.test.ts
+++ b/packages/functions/src/__tests__/unreferencedReusableObject.test.ts
@@ -34,7 +34,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" function has invalid options specified. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -44,7 +44,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" function has invalid options specified. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -54,7 +54,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" function is missing "reusableObjectsLocation" option. Example valid options: { "reusableObjectsLocation": "#/components/schemas" }, { "reusableObjectsLocation": "#/$defs" }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -67,7 +67,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" function does not support "foo" option',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'foo'],
           ),
         ],
       ],
@@ -79,7 +79,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" and its "reusableObjectsLocation" option support only valid JSON Pointer fragments, i.e. "#", "#/foo", "#/paths/~1user"',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'reusableObjectsLocation'],
           ),
         ],
       ],
@@ -91,7 +91,7 @@ describe('Core Functions / UnreferencedReusableObject', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"unreferencedReusableObject" and its "reusableObjectsLocation" option support only valid JSON Pointer fragments, i.e. "#", "#/foo", "#/paths/~1user"',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'reusableObjectsLocation'],
           ),
         ],
       ],
diff --git a/packages/functions/src/__tests__/xor.test.ts b/packages/functions/src/__tests__/xor.test.ts
index 2bf7deed..6ea29e23 100644
--- a/packages/functions/src/__tests__/xor.test.ts
+++ b/packages/functions/src/__tests__/xor.test.ts
@@ -73,7 +73,7 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" function has invalid options specified. Example valid options: { "properties": ["id", "name"] }, { "properties": ["country", "street"] }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
@@ -83,13 +83,21 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" function has invalid options specified. Example valid options: { "properties": ["id", "name"] }, { "properties": ["country", "street"] }',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions'],
           ),
         ],
       ],
       [
         { properties: ['foo', 'bar'], foo: true },
-        [new RulesetValidationError('invalid-function-options', '"xor" function does not support "foo" option', [])],
+        [
+          new RulesetValidationError('invalid-function-options', '"xor" function does not support "foo" option', [
+            'rules',
+            'my-rule',
+            'then',
+            'functionOptions',
+            'foo',
+          ]),
+        ],
       ],
       [
         { properties: ['foo', 'bar', 'baz'] },
@@ -97,7 +105,7 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'properties'],
           ),
         ],
       ],
@@ -107,7 +115,7 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'properties'],
           ),
         ],
       ],
@@ -117,7 +125,7 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'properties'],
           ),
         ],
       ],
@@ -127,7 +135,7 @@ describe('Core Functions / Xor', () => {
           new RulesetValidationError(
             'invalid-function-options',
             '"xor" and its "properties" option support 2-item tuples, i.e. ["id", "name"]',
-            [],
+            ['rules', 'my-rule', 'then', 'functionOptions', 'properties'],
           ),
         ],
       ],