Skip to content

Commit

Permalink
feat(core): support path aliases in rulesets (#1692)
Browse files Browse the repository at this point in the history
* feat(core): support path aliases in rulesets

* chore: detect circular aliases

* revert: keep the promise left in a comment
  • Loading branch information
P0lip authored Jun 28, 2021
1 parent 153d685 commit 3a112b8
Show file tree
Hide file tree
Showing 12 changed files with 803 additions and 62 deletions.
1 change: 1 addition & 0 deletions docs/reference/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

- a _valid_ JSON Path expression throws any exception
- message interpolation fails
- a JSON Path alias cannot be resolved

**WILL report a diagnostic error or errors** when:

Expand Down
21 changes: 19 additions & 2 deletions packages/core/src/meta/ruleset.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
},
"parserOptions": {
"$ref": "#/properties/parserOptions"
},
"aliases": {
"$ref": "#/properties/aliases"
}
},
"anyOf": [
Expand All @@ -114,8 +117,22 @@
]
},
"errorMessage": {
"minItems": "must not be empty",
"type": "must be an array"
"minItems": "must not be empty"
}
},
"aliases": {
"type": "object",
"propertyNames": {
"pattern": "^[A-Za-z][A-Za-z0-9_-]*$",
"errorMessage": {
"pattern": "to avoid confusion the name must match /^[A-Za-z][A-Za-z0-9_-]*$/ regular expression",
"minLength": "the name of an alias must not be empty"
}
},
"additionalProperties": {
"type": "string",
"pattern": "^[$#]",
"errorMessage": "the value of an alias must be a valid JSON Path expression or a reference to the existing Alias optionally paired with a JSON Path expression subset"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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'],
rules: {
'falsy-value': {
given: '#Value',
severity: DiagnosticSeverity.Warning,
then: {
function: falsy,
},
},
},
},
],
};
17 changes: 11 additions & 6 deletions packages/core/src/ruleset/__tests__/__helpers__/print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ export function print(ruleset: Ruleset | RulesetDefinition): string {
);
}

function formatRules(ruleset: Ruleset): Record<string, Partial<Record<keyof Rule | 'inherited', string>>> {
function formatRules(
ruleset: Ruleset,
): Record<string, Partial<Record<keyof Rule | 'inherited', string | Record<string, string>>>> {
const { rules } = ruleset;
const formattedRules: Record<string, Partial<Record<keyof Rule | 'inherited', string>>> = {};
const formattedRules: Record<string, Partial<Record<keyof Rule | 'inherited', string | Record<string, string>>>> = {};
for (const rule of Object.values(rules)) {
formattedRules[rule.name] = {
name: rule.name,
enabled: String(rule.enabled),
inherited: String(rule.owner !== ruleset && (ruleset.source === null || rule.owner.source !== ruleset.source)),
...(rule.formats && rule.formats.size > 0 ? { formats: printFormats([...rule.formats]) } : null),
given: printArray(rule.given),
severity: String(rule.severity),
...(rule.documentationUrl !== null ? { documentationUrl: rule.documentationUrl } : null),
};
Expand All @@ -38,8 +41,10 @@ function formatRules(ruleset: Ruleset): Record<string, Partial<Record<keyof Rule
return formattedRules;
}

function printFormats(formats: Format[]): string {
return Array.from(formats)
.map(fn => fn.displayName ?? fn.name)
.join(', ');
function printFormats(formats: Format[]): Record<string, string> {
return printArray(Array.from(formats).map(fn => fn.displayName ?? fn.name));
}

function printArray(array: string[]): Record<string, string> {
return Object.fromEntries(Object.entries(array));
}
Loading

0 comments on commit 3a112b8

Please sign in to comment.