diff --git a/packages/core/src/ruleset/__tests__/ruleset.test.ts b/packages/core/src/ruleset/__tests__/ruleset.test.ts index 3f22369c7..520cea441 100644 --- a/packages/core/src/ruleset/__tests__/ruleset.test.ts +++ b/packages/core/src/ruleset/__tests__/ruleset.test.ts @@ -10,7 +10,7 @@ import { RulesetValidationError } from '../validation/index'; import { isPlainObject } from '@stoplight/json'; import { Format } from '../format'; import type { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema'; -import { FormatsSet } from '../utils/formatsSet'; +import { Formats } from '../formats'; async function loadRuleset(mod: Promise<{ default: RulesetDefinition }>, source?: string): Promise { return new Ruleset((await mod).default, { source }); @@ -1378,24 +1378,22 @@ describe('Ruleset', () => { }, }); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([draft4]))).toStrictEqual(['$..id']); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([draft6]))).toStrictEqual(['$..$id']); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([draft7]))).toStrictEqual(['$..$id']); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([draft6, draft7]))).toStrictEqual([ - '$..$id', - ]); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([draft4]))).toStrictEqual(['$..id']); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([draft6]))).toStrictEqual(['$..$id']); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([draft7]))).toStrictEqual(['$..$id']); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([draft6, draft7]))).toStrictEqual(['$..$id']); - expect(ruleset.rules['valid-parameter'].getGivenForFormats(new FormatsSet([oas2]))).toStrictEqual([ + expect(ruleset.rules['valid-parameter'].getGivenForFormats(new Formats([oas2]))).toStrictEqual([ '$.parameters[*]', '$.paths[*].parameters[?(@ && !@.$ref)]', '$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@ && !@.$ref)]', ]); - expect(ruleset.rules['valid-parameter'].getGivenForFormats(new FormatsSet([oas3]))).toStrictEqual([ + expect(ruleset.rules['valid-parameter'].getGivenForFormats(new Formats([oas3]))).toStrictEqual([ '$.components.parameters[*]', '$.paths[*].parameters[?(@ && !@.$ref)]', '$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@ && !@.$ref)]', ]); - expect(ruleset.rules['valid-parameter'].getGivenForFormats(new FormatsSet([oas2, oas3]))).toStrictEqual([ + expect(ruleset.rules['valid-parameter'].getGivenForFormats(new Formats([oas2, oas3]))).toStrictEqual([ '$.components.parameters[*]', '$.paths[*].parameters[?(@ && !@.$ref)]', '$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@ && !@.$ref)]', @@ -1442,7 +1440,7 @@ describe('Ruleset', () => { }, }); - expect(() => ruleset.rules['valid-header'].getGivenForFormats(new FormatsSet([oas3]))).toThrowError( + expect(() => ruleset.rules['valid-header'].getGivenForFormats(new Formats([oas3]))).toThrowError( ReferenceError( 'Alias "HeaderObject" is circular. Resolution stack: HeaderObject -> HeaderObjects -> Components -> HeaderObject', ), @@ -1479,8 +1477,8 @@ describe('Ruleset', () => { }, }); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([draft7]))).toStrictEqual([]); - expect(ruleset.rules['valid-id'].getGivenForFormats(new FormatsSet([]))).toStrictEqual([]); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([draft7]))).toStrictEqual([]); + expect(ruleset.rules['valid-id'].getGivenForFormats(new Formats([]))).toStrictEqual([]); }); it('should be serializable', () => { diff --git a/packages/core/src/ruleset/utils/formatsSet.ts b/packages/core/src/ruleset/formats.ts similarity index 61% rename from packages/core/src/ruleset/utils/formatsSet.ts rename to packages/core/src/ruleset/formats.ts index 0be49eafb..c8a9063cc 100644 --- a/packages/core/src/ruleset/utils/formatsSet.ts +++ b/packages/core/src/ruleset/formats.ts @@ -1,10 +1,10 @@ -import type { Format } from '../format'; +import type { Format } from './format'; function printFormat(format: Format): string { return format.displayName ?? format.name; } -export class FormatsSet extends Set { +export class Formats extends Set { public toJSON(): string[] { return Array.from(this).map(printFormat); } diff --git a/packages/core/src/ruleset/index.ts b/packages/core/src/ruleset/index.ts index 3a38776e2..50addc0e8 100644 --- a/packages/core/src/ruleset/index.ts +++ b/packages/core/src/ruleset/index.ts @@ -1,5 +1,5 @@ export { assertValidRuleset, RulesetValidationError } from './validation/index'; -export { getDiagnosticSeverity } from './utils'; +export { getDiagnosticSeverity } from './utils/severity'; export { createRulesetFunction, SchemaDefinition as RulesetFunctionSchemaDefinition } from './function'; export { Format } from './format'; export { RulesetDefinition, RuleDefinition, ParserOptions, HumanReadableDiagnosticSeverity } from './types'; diff --git a/packages/core/src/ruleset/rule.ts b/packages/core/src/ruleset/rule.ts index a97d1b03b..d7a73608a 100644 --- a/packages/core/src/ruleset/rule.ts +++ b/packages/core/src/ruleset/rule.ts @@ -13,9 +13,10 @@ import type { RuleDefinition, RulesetAliasesDefinition, RulesetScopedAliasDefinition, + Stringifable, } from './types'; import { minimatch } from './utils/minimatch'; -import { FormatsSet } from './utils/formatsSet'; +import { Formats } from './formats'; import { isSimpleAliasDefinition } from './utils/guards'; const ALIAS = /^#([A-Za-z0-9_-]+)/; @@ -25,7 +26,7 @@ export interface IRule { message: string | null; severity: DiagnosticSeverity; resolved: boolean; - formats: Set | null; + formats: Formats | null; enabled: boolean; recommended: boolean; documentationUrl: string | null; @@ -35,7 +36,7 @@ export interface IRule { export type StringifiedRule = Omit & { name: string; - formats: FormatsSet | null; + formats: string[] | null; then: (Pick & { function: string; functionOptions?: string })[]; owner: number; }; @@ -45,7 +46,7 @@ export class Rule implements IRule { public message: string | null; #severity!: DiagnosticSeverity; public resolved: boolean; - public formats: FormatsSet | null; + public formats: Formats | null; #enabled: boolean; public recommended: boolean; public documentationUrl: string | null; @@ -64,7 +65,7 @@ export class Rule implements IRule { this.documentationUrl = definition.documentationUrl ?? null; this.severity = definition.severity; this.resolved = definition.resolved !== false; - this.formats = 'formats' in definition ? new FormatsSet(definition.formats) : null; + this.formats = 'formats' in definition ? new Formats(definition.formats) : null; this.then = definition.then; this.given = definition.given; } @@ -241,7 +242,7 @@ export class Rule implements IRule { return new Rule(this.name, this.definition, this.owner); } - public toJSON(): StringifiedRule { + public toJSON(): Stringifable { return { name: this.name, recommended: this.recommended, diff --git a/packages/core/src/ruleset/ruleset.ts b/packages/core/src/ruleset/ruleset.ts index cc123c490..00075dff1 100644 --- a/packages/core/src/ruleset/ruleset.ts +++ b/packages/core/src/ruleset/ruleset.ts @@ -1,20 +1,21 @@ import { dirname, relative } from '@stoplight/path'; +import { isPlainObject, extractPointerFromRef, extractSourceFromRef } from '@stoplight/json'; +import { DiagnosticSeverity } from '@stoplight/types'; import { minimatch } from './utils/minimatch'; import { Rule, StringifiedRule } from './rule'; -import { +import type { FileRulesetSeverityDefinition, ParserOptions, RulesetAliasesDefinition, RulesetDefinition, RulesetOverridesDefinition, + Stringifable, } from './types'; import { assertValidRuleset } from './validation/index'; import { mergeRule } from './mergers/rules'; import { DEFAULT_PARSER_OPTIONS, getDiagnosticSeverity } from '..'; import { mergeRulesets } from './mergers/rulesets'; -import { isPlainObject, extractPointerFromRef, extractSourceFromRef } from '@stoplight/json'; -import { DiagnosticSeverity } from '@stoplight/types'; -import { FormatsSet } from './utils/formatsSet'; +import { Formats } from './formats'; import { isSimpleAliasDefinition } from './utils/guards'; const STACK_SYMBOL = Symbol('@stoplight/spectral/ruleset/#stack'); @@ -33,7 +34,7 @@ export type StringifiedRuleset = { extends: StringifiedRuleset[] | null; source: string | null; aliases: RulesetAliasesDefinition | null; - formats: FormatsSet | null; + formats: Formats | null; rules: Record; overrides: RulesetOverridesDefinition | null; parserOptions: ParserOptions; @@ -41,7 +42,7 @@ export type StringifiedRuleset = { export class Ruleset { public readonly id = SEED++; - public readonly formats = new FormatsSet(); + public readonly formats = new Formats(); public readonly overrides: RulesetOverridesDefinition | null; public readonly aliases: RulesetAliasesDefinition | null; public readonly hasComplexAliases: boolean; @@ -84,7 +85,7 @@ export class Ruleset { hasComplexAliases = true; const targets = value.targets.map(target => ({ - formats: new FormatsSet(target.formats), + formats: new Formats(target.formats), given: target.given, })); @@ -265,10 +266,7 @@ export class Ruleset { return ruleset; } - public toJSON(): Omit & { - extends: Ruleset['extends']; - rules: Ruleset['rules']; - } { + public toJSON(): Stringifable { return { id: this.id, extends: this.extends, @@ -308,10 +306,9 @@ export class Ruleset { this.formats.add(format); } } else if (rule.owner !== this) { - rule.formats = - rule.owner.definition.formats === void 0 ? null : new FormatsSet(rule.owner.definition.formats); + rule.formats = rule.owner.definition.formats === void 0 ? null : new Formats(rule.owner.definition.formats); } else if (this.definition.formats !== void 0) { - rule.formats = new FormatsSet(this.definition.formats); + rule.formats = new Formats(this.definition.formats); } if (this.definition.documentationUrl !== void 0 && rule.documentationUrl === null) { diff --git a/packages/core/src/ruleset/types.ts b/packages/core/src/ruleset/types.ts index f9882ee94..381a18d10 100644 --- a/packages/core/src/ruleset/types.ts +++ b/packages/core/src/ruleset/types.ts @@ -1,7 +1,7 @@ -import { DiagnosticSeverity } from '@stoplight/types'; -import { Format } from './format'; -import { RulesetFunction, RulesetFunctionWithValidator } from '../types'; -import { FormatsSet } from './utils/formatsSet'; +import type { DiagnosticSeverity } from '@stoplight/types'; +import type { Format } from './format'; +import type { RulesetFunction, RulesetFunctionWithValidator } from '../types'; +import type { Formats } from './formats'; export type HumanReadableDiagnosticSeverity = 'error' | 'warn' | 'info' | 'hint' | 'off'; export type FileRuleSeverityDefinition = DiagnosticSeverity | HumanReadableDiagnosticSeverity | boolean; @@ -17,7 +17,7 @@ export type ParserOptions = { export type RuleDefinition = { type?: 'validation' | 'style'; - formats?: Format[]; + formats?: Formats | Format[]; documentationUrl?: string; @@ -81,7 +81,7 @@ export type RulesetOverridesDefinition = ReadonlyArray<{ files: string[] } & Rul export type RulesetScopedAliasDefinition = { description?: string; targets: { - formats: FormatsSet | Format[]; + formats: Formats | Format[]; given: string[]; }[]; }; @@ -92,7 +92,7 @@ export type RulesetDefinition = Readonly< { documentationUrl?: string; description?: string; - formats?: FormatsSet | Format[]; + formats?: Formats | Format[]; parserOptions?: Partial; overrides?: RulesetOverridesDefinition; aliases?: RulesetAliasesDefinition; @@ -112,3 +112,10 @@ export type RulesetDefinition = Readonly< } > >; + +// eslint-disable-next-line @typescript-eslint/ban-types +export type Stringifable = T extends object + ? { + [P in keyof T]: Stringifable | { toJSON?(): Stringifable }; + } + : T; diff --git a/packages/core/src/ruleset/utils/index.ts b/packages/core/src/ruleset/utils/index.ts deleted file mode 100644 index f2f24e222..000000000 --- a/packages/core/src/ruleset/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './severity';