Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { pattern } from '@stoplight/spectral-functions';
import { DiagnosticSeverity } from '@stoplight/types';
import { RulesetDefinition } from '@stoplight/spectral-core';

export { ruleset as default };

const ruleset: RulesetDefinition = {
aliases: {
infoSection: ['$.info.section'],
},
rules: {
'check-description': {
message: 'API version must be 1.0.0',
given: '#infoSection',
severity: DiagnosticSeverity.Error,
then: {
field: 'description',
function: pattern,
functionOptions: {
match: 'Stoplight',
},
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RulesetDefinition } from '@stoplight/spectral-core';

import _scope from './scope';
import _desc from './description-check';


export { ruleset as default };

const ruleset: RulesetDefinition = {
extends: [
_scope,
_desc,
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RulesetDefinition } from '@stoplight/spectral-core';

import _scope from './scope';
import _version from './version-check';


export { ruleset as default };

const ruleset: RulesetDefinition = {
extends: [
_scope,
_version,
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DiagnosticSeverity } from '@stoplight/types';
import { falsy, pattern, truthy } from '@stoplight/spectral-functions';
import { RulesetDefinition } from '@stoplight/spectral-core';

export { ruleset as default };

const ruleset: RulesetDefinition = {
aliases: {
Stoplight: ['$..stoplight'],
},
overrides: [
{
files: ['*.yaml'],
rules: {
'value-matches-stoplight': {
message: 'Value must contain Stoplight',
given: '#Stoplight',
severity: DiagnosticSeverity.Error,
then: {
field: 'description',
function: pattern,
functionOptions: {
match: 'Stoplight',
},
},
},
},
},
{
files: ['**/*.json'],
aliases: {
Value: ['$..value'],
},
rules: {
'truthy-stoplight-property': {
message: 'Value must contain Stoplight',
given: '#Value',
severity: DiagnosticSeverity.Error,
then: {
function: truthy,
},
},
},
},
{
files: ['legacy/**/*.json'],
aliases: {
Value: ['$..value'],
},
rules: {
'falsy-value': {
given: '#Value',
severity: DiagnosticSeverity.Warning,
then: {
function: falsy,
},
},
},
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { pattern } from '@stoplight/spectral-functions';
import { DiagnosticSeverity } from '@stoplight/types';
import { RulesetDefinition } from '@stoplight/spectral-core';

export { ruleset as default };

const ruleset: RulesetDefinition = {
aliases: {
infoSection: ['$.info'],
},
rules: {
'check-initial-version': {
message: 'API version must be 1.0.0',
given: '#infoSection',
severity: DiagnosticSeverity.Error,
then: {
field: 'version',
function: pattern,
functionOptions: {
match: '^1\\.0\\.0$',
},
},
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { assertValidRuleset, RulesetValidationError } from '../index';
import AggregateError = require('es-aggregate-error');
import invalidRuleset from './__fixtures__/invalid-ruleset';
import validRuleset from './__fixtures__/valid-flat-ruleset';
import extendedRuleset from './__fixtures__/aliases/extended-definition';
import aliasCollisionRuleset from './__fixtures__/aliases/extended-alias-collision';

import type { Format } from '../../format';
import { RulesetDefinition, RulesetOverridesDefinition } from '../../types';
Expand Down Expand Up @@ -63,6 +65,14 @@ describe('JS Ruleset Validation', () => {
expect(assertValidRuleset.bind(null, validRuleset)).not.toThrow();
});

it('given valid ruleset extending ruleset with alias should, emits no errors', () => {
expect(assertValidRuleset.bind(null, extendedRuleset)).not.toThrow();
});

it('given valid ruleset extending rulesets with alias collision should, emits no errors', () => {
expect(assertValidRuleset.bind(null, aliasCollisionRuleset)).not.toThrow();
});

it.each([false, 2, null, 'foo', '12.foo.com'])(
'given invalid %s documentationUrl in a rule, throws',
documentationUrl => {
Expand Down
56 changes: 48 additions & 8 deletions packages/core/src/ruleset/validation/validators/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,54 @@ function getOverrides(overrides: unknown, key: string): Record<string, unknown>
return isPlainObject(actualOverrides) && isPlainObject(actualOverrides.aliases) ? actualOverrides.aliases : null;
}

function getExtended(extended: Record<string, unknown>, parsedPath: string[]): Record<string, unknown> | null {
if (!Array.isArray(extended)) return null;

const key = parsedPath[1];
const index = Number(key);
if (Number.isNaN(index)) return null;
if (index < 0 && index >= extended.length) return null;

const actualExtended: Record<string, unknown> = extended[index] as Record<string, unknown>;
const aliases =
isPlainObject(actualExtended) && isPlainObject(actualExtended.aliases) ? actualExtended.aliases : null;

if (parsedPath.length >= 4 && parsedPath[2] === 'overrides') {
return {
...aliases,
...getOverrides(actualExtended.overrides, parsedPath[3]),
};
}

return aliases;
}

function getResolvedAliases(
parsedPath: string[],
ruleset: {
aliases?: Record<string, unknown>;
overrides?: Record<string, unknown>;
extends?: Record<string, unknown>;
},
) {
if (parsedPath[0] === 'extends') {
return getExtended(ruleset.extends as Record<string, unknown>, parsedPath);
} else if (parsedPath[0] === 'overrides') {
return {
...ruleset.aliases,
...getOverrides(ruleset.overrides, parsedPath[1]),
};
} else {
return ruleset.aliases;
}
}

export function validateAlias(
ruleset: { aliases?: Record<string, unknown>; overrides?: Record<string, unknown> },
ruleset: {
aliases?: Record<string, unknown>;
overrides?: Record<string, unknown>;
extends?: Record<string, unknown>;
},
alias: string,
path: string,
): Error | void {
Expand All @@ -26,13 +72,7 @@ export function validateAlias(
try {
const formats: unknown = get(ruleset, [...parsedPath.slice(0, parsedPath.indexOf('rules') + 2), 'formats']);

const aliases =
parsedPath[0] === 'overrides'
? {
...ruleset.aliases,
...getOverrides(ruleset.overrides, parsedPath[1]),
}
: ruleset.aliases;
const aliases = getResolvedAliases(parsedPath, ruleset);

resolveAlias(aliases ?? null, alias, Array.isArray(formats) ? new Formats(formats) : null);
} catch (ex) {
Expand Down