Skip to content

Commit

Permalink
chore(core): improve ruleset typings (#2132)
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Apr 27, 2022
1 parent 6def6be commit ec08efe
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 69 deletions.
13 changes: 8 additions & 5 deletions packages/core/package.json
Expand Up @@ -19,11 +19,15 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js"
"default": "./dist/index.js"
},
"./ruleset": {
"types": "./dist/ruleset/index.d.ts",
"default": "./dist/ruleset/index.js"
},
"./ruleset/validation": {
"types": "./dist/ruleset/validation/index.d.ts",
"require": "./dist/ruleset/validation/index.js"
"default": "./dist/ruleset/validation/index.js"
}
},
"engines": {
Expand All @@ -40,12 +44,12 @@
"@stoplight/spectral-parsers": "^1.0.0",
"@stoplight/spectral-ref-resolver": "^1.0.0",
"@stoplight/spectral-runtime": "^1.0.0",
"@stoplight/types": "12.3.0",
"@stoplight/types": "13.0.0",
"@types/json-schema": "^7.0.7",
"ajv": "^8.6.0",
"ajv-errors": "~3.0.0",
"ajv-formats": "~2.1.0",
"blueimp-md5": "2.18.0",
"json-schema": "0.4.0",
"jsonpath-plus": "6.0.1",
"lodash": "~4.17.21",
"lodash.topath": "^4.5.2",
Expand All @@ -60,7 +64,6 @@
"@stoplight/spectral-functions": "*",
"@stoplight/spectral-parsers": "*",
"@stoplight/yaml": "^4.2.2",
"@types/json-schema": "^7.0.7",
"@types/minimatch": "^3.0.5",
"@types/treeify": "^1.0.0",
"nock": "^13.1.0",
Expand Down
26 changes: 12 additions & 14 deletions packages/core/src/ruleset/__tests__/ruleset.test.ts
Expand Up @@ -9,8 +9,8 @@ import { print } from './__helpers__/print';
import { RulesetValidationError } from '../validation/index';
import { isPlainObject } from '@stoplight/json';
import { Format } from '../format';
import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
import { FormatsSet } from '../utils/formatsSet';
import type { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
import { Formats } from '../formats';

async function loadRuleset(mod: Promise<{ default: RulesetDefinition }>, source?: string): Promise<Ruleset> {
return new Ruleset((await mod).default, { source });
Expand Down Expand Up @@ -1390,24 +1390,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)]',
Expand Down Expand Up @@ -1454,7 +1452,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',
),
Expand Down Expand Up @@ -1491,8 +1489,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', () => {
Expand Down
@@ -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<T extends Format = Format> extends Set<T> {
export class Formats<T extends Format = Format> extends Set<T> {
public toJSON(): string[] {
return Array.from(this).map(printFormat);
}
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions packages/core/src/ruleset/index.ts
@@ -1,7 +1,7 @@
export { assertValidRuleset, RulesetValidationError } from './validation/index';
export { getDiagnosticSeverity } from './utils';
export { createRulesetFunction, SchemaDefinition as RulesetFunctionSchemaDefinition } from './rulesetFunction';
export { getDiagnosticSeverity } from './utils/severity';
export { createRulesetFunction, SchemaDefinition as RulesetFunctionSchemaDefinition } from './function';
export { Format } from './format';
export { RulesetDefinition, RuleDefinition, ParserOptions, HumanReadableDiagnosticSeverity } from './types';
export { Ruleset } from './ruleset';
export { Rule } from './rule';
export { Ruleset, StringifiedRuleset } from './ruleset';
export { Rule, StringifiedRule } from './rule';
13 changes: 7 additions & 6 deletions packages/core/src/ruleset/rule.ts
Expand Up @@ -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_-]+)/;
Expand All @@ -25,7 +26,7 @@ export interface IRule {
message: string | null;
severity: DiagnosticSeverity;
resolved: boolean;
formats: Set<Format> | null;
formats: Formats | null;
enabled: boolean;
recommended: boolean;
documentationUrl: string | null;
Expand All @@ -35,7 +36,7 @@ export interface IRule {

export type StringifiedRule = Omit<IRule, 'formats' | 'then'> & {
name: string;
formats: FormatsSet | null;
formats: string[] | null;
then: (Pick<IRuleThen, 'field'> & { function: string; functionOptions?: string })[];
owner: number;
};
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -241,7 +242,7 @@ export class Rule implements IRule {
return new Rule(this.name, this.definition, this.owner);
}

public toJSON(): StringifiedRule {
public toJSON(): Stringifable<StringifiedRule> {
return {
name: this.name,
recommended: this.recommended,
Expand Down
27 changes: 12 additions & 15 deletions 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';
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');
Expand All @@ -33,7 +34,7 @@ export type StringifiedRuleset = {
extends: StringifiedRuleset[] | null;
source: string | null;
aliases: RulesetAliasesDefinition | null;
formats: FormatsSet | null;
formats: Formats | null;
rules: Record<string, StringifiedRule>;
overrides: RulesetOverridesDefinition | null;
parserOptions: ParserOptions;
Expand All @@ -43,7 +44,7 @@ export class Ruleset {
public readonly id = SEED++;

protected readonly extends: Ruleset[] | null;
public readonly formats = new FormatsSet();
public readonly formats = new Formats();
public readonly overrides: RulesetOverridesDefinition | null;
public readonly aliases: RulesetAliasesDefinition | null;
public readonly hasComplexAliases: boolean;
Expand Down Expand Up @@ -86,7 +87,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,
}));

Expand Down Expand Up @@ -286,10 +287,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) {
Expand All @@ -309,10 +309,7 @@ export class Ruleset {
return DEFAULT_RULESET_FILE.test(uri);
}

public toJSON(): Omit<StringifiedRuleset, 'extends' | 'rules'> & {
extends: Ruleset['extends'];
rules: Ruleset['rules'];
} {
public toJSON(): Stringifable<StringifiedRuleset> {
return {
id: this.id,
extends: this.extends,
Expand Down
21 changes: 14 additions & 7 deletions 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;
Expand All @@ -17,7 +17,7 @@ export type ParserOptions = {
export type RuleDefinition = {
type?: 'validation' | 'style';

formats?: Format[];
formats?: Formats | Format[];

documentationUrl?: string;

Expand Down Expand Up @@ -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[];
}[];
};
Expand All @@ -92,7 +92,7 @@ export type RulesetDefinition = Readonly<
{
documentationUrl?: string;
description?: string;
formats?: FormatsSet | Format[];
formats?: Formats | Format[];
parserOptions?: Partial<ParserOptions>;
overrides?: RulesetOverridesDefinition;
aliases?: RulesetAliasesDefinition;
Expand All @@ -112,3 +112,10 @@ export type RulesetDefinition = Readonly<
}
>
>;

// eslint-disable-next-line @typescript-eslint/ban-types
export type Stringifable<T> = T extends object
? {
[P in keyof T]: Stringifable<T[P]> | { toJSON?(): Stringifable<T[P]> };
}
: T;
1 change: 0 additions & 1 deletion packages/core/src/ruleset/utils/index.ts

This file was deleted.

4 changes: 3 additions & 1 deletion tsconfig.build.json
@@ -1,7 +1,9 @@
{
"include": [],
"extends": "./tsconfig.json",
"composite": true,
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./packages/cli/tsconfig.build.json"
Expand Down

0 comments on commit ec08efe

Please sign in to comment.