11import type { ErrorObject } from 'ajv' ;
2- import type { IDiagnostic , JsonPath } from '@stoplight/types' ;
2+ import type { IDiagnostic , IRange , JsonPath } from '@stoplight/types' ;
33import { isAggregateError } from '../../guards/isAggregateError' ;
44
55export type RulesetValidationErrorCode =
@@ -17,24 +17,40 @@ export type RulesetValidationErrorCode =
1717 | 'undefined-function'
1818 | 'undefined-alias' ;
1919
20+ export type RulesetSourceContext = {
21+ readonly source : string ;
22+ getLocationForJsonPath ( path : JsonPath ) : { range : IRange } | undefined ;
23+ } ;
24+
2025interface IRulesetValidationSingleError extends Pick < IDiagnostic , 'message' | 'path' > {
2126 readonly code : RulesetValidationErrorCode ;
27+ readonly range ?: IRange ;
28+ readonly source ?: string ;
2229}
2330
2431export class RulesetValidationError extends Error implements IRulesetValidationSingleError {
32+ public readonly range ?: IRange ;
33+ public readonly source ?: string ;
34+
2535 constructor (
2636 public readonly code : RulesetValidationErrorCode ,
2737 public readonly message : string ,
2838 public readonly path : JsonPath ,
39+ location ?: { range ?: IRange ; source ?: string } ,
2940 ) {
3041 super ( message ) ;
42+ this . range = location ?. range ;
43+ this . source = location ?. source ;
3144 }
3245}
3346
3447const RULE_INSTANCE_PATH = / ^ \/ r u l e s \/ [ ^ / ] + / ;
3548const GENERIC_INSTANCE_PATH = / ^ \/ (?: a l i a s e s | e x t e n d s | o v e r r i d e s (?: \/ \d + \/ e x t e n d s ) ? ) / ;
3649
37- export function convertAjvErrors ( errors : ErrorObject [ ] ) : RulesetValidationError [ ] {
50+ export function convertAjvErrors (
51+ errors : ErrorObject [ ] ,
52+ sourceContext ?: RulesetSourceContext ,
53+ ) : RulesetValidationError [ ] {
3854 const sortedErrors = [ ...errors ]
3955 . sort ( ( errorA , errorB ) => {
4056 const diff = errorA . instancePath . length - errorB . instancePath . length ;
@@ -78,11 +94,18 @@ export function convertAjvErrors(errors: ErrorObject[]): RulesetValidationError[
7894
7995 return filteredErrors . flatMap ( error => {
8096 if ( error . keyword === 'x-spectral-runtime' ) {
81- return flatErrors ( error . params . errors ) ;
97+ const flat = flatErrors ( error . params . errors ) ;
98+ const list = Array . isArray ( flat ) ? flat : [ flat ] ;
99+ return list . map ( e => enrichWithLocation ( e , sourceContext ) ) ;
82100 }
83101
84102 const path = error . instancePath . slice ( 1 ) . split ( '/' ) ;
85- return new RulesetValidationError ( inferErrorCode ( path , error . keyword ) , error . message ?? 'unknown error' , path ) ;
103+ return new RulesetValidationError (
104+ inferErrorCode ( path , error . keyword ) ,
105+ error . message ?? 'unknown error' ,
106+ path ,
107+ resolveLocation ( path , sourceContext ) ,
108+ ) ;
86109 } ) ;
87110}
88111
@@ -94,6 +117,31 @@ function flatErrors(error: RulesetValidationError | AggregateError): RulesetVali
94117 return error ;
95118}
96119
120+ function resolveLocation (
121+ path : JsonPath ,
122+ sourceContext : RulesetSourceContext | undefined ,
123+ ) : { range ?: IRange ; source ?: string } | undefined {
124+ if ( sourceContext === undefined ) {
125+ return undefined ;
126+ }
127+
128+ return {
129+ source : sourceContext . source ,
130+ range : sourceContext . getLocationForJsonPath ( path ) ?. range ,
131+ } ;
132+ }
133+
134+ function enrichWithLocation (
135+ error : RulesetValidationError ,
136+ sourceContext : RulesetSourceContext | undefined ,
137+ ) : RulesetValidationError {
138+ if ( sourceContext === undefined || error . source !== undefined || error . range !== undefined ) {
139+ return error ;
140+ }
141+
142+ return new RulesetValidationError ( error . code , error . message , error . path , resolveLocation ( error . path , sourceContext ) ) ;
143+ }
144+
97145function inferErrorCode ( path : string [ ] , keyword : string ) : RulesetValidationErrorCode {
98146 if ( path . length === 0 ) {
99147 return 'generic-validation-error' ;
0 commit comments