Skip to content

Commit

Permalink
fix(core): improve deep ruleset inheritance (stoplightio#2326)
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip authored Nov 22, 2022
1 parent da33ad6 commit 378b4b8
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { DiagnosticSeverity } from '@stoplight/types';
import { truthy } from '@stoplight/spectral-functions';

const ruleset1 = {
rules: {
'custom-info-description': {
message: 'API Description is missing',
severity: DiagnosticSeverity.Error,
given: '$.info',
then: {
field: 'description',
function: truthy,
},
},
},
};

const ruleset2 = {
extends: [ruleset1],
rules: {},
};

export default {
extends: [[ruleset2, 'off']],
rules: {},
};
7 changes: 7 additions & 0 deletions packages/core/src/ruleset/__tests__/ruleset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ describe('Ruleset', () => {
expect(getEnabledRules(rules)).toEqual(['overridable-rule']);
});

it('given nested extends with severity set to off #2', async () => {
const { rules } = await loadRuleset(import('./__fixtures__/severity/off-proxy2'));
expect(Object.keys(rules)).toEqual(['custom-info-description']);

expect(getEnabledRules(rules)).toEqual([]);
});

it('given nested extends with severity set to off and explicit override to error', async () => {
const { rules } = await loadRuleset(import('./__fixtures__/severity/error'));
expect(Object.keys(rules)).toEqual([
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/ruleset/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { HumanReadableDiagnosticSeverity, IRuleThen, RuleDefinition, String
import { minimatch } from './utils/minimatch';
import { Formats } from './formats';
import { resolveAlias } from './alias';
import type { Stringified } from './types';
import type { Stringified, FileRulesetSeverityDefinition } from './types';

export interface IRule {
description: string | null;
Expand Down Expand Up @@ -73,6 +73,10 @@ export class Rule implements IRule {
this.#enabled = enabled;
}

public static isEnabled(rule: IRule, severity: FileRulesetSeverityDefinition): boolean {
return severity === 'all' || (severity === 'recommended' && rule.recommended);
}

public getSeverityForSource(source: string, path: JsonPath): DiagnosticSeverity | -1 {
if (this.overrides === void 0 || this.overrides.definition.size === 0) {
return this.severity;
Expand Down
19 changes: 15 additions & 4 deletions packages/core/src/ruleset/ruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import { isSimpleAliasDefinition } from './utils/guards';
import type { Stringified } from './types';

const STACK_SYMBOL = Symbol('@stoplight/spectral/ruleset/#stack');
const EXPLICIT_SEVERITY = Symbol('@stoplight/spectral/ruleset/#explicit-severity');
const DEFAULT_RULESET_FILE = /^\.?spectral\.(ya?ml|json|m?js)$/;

type RulesetContext = {
readonly severity?: FileRulesetSeverityDefinition;
readonly source?: string;
readonly [STACK_SYMBOL]?: Map<RulesetDefinition, Ruleset>;
readonly [EXPLICIT_SEVERITY]?: boolean;
};

let SEED = 1;
Expand Down Expand Up @@ -110,8 +112,9 @@ export class Ruleset {
(extensions, extension) => {
let actualExtension;
let severity: FileRulesetSeverityDefinition = 'recommended';
const explicitSeverity = Array.isArray(extension);

if (Array.isArray(extension)) {
if (explicitSeverity) {
[actualExtension, severity] = extension;
} else {
actualExtension = extension;
Expand All @@ -123,7 +126,13 @@ export class Ruleset {
return extensions;
}

extensions.push(new Ruleset(actualExtension, { severity, [STACK_SYMBOL]: stack }));
extensions.push(
new Ruleset(actualExtension, {
severity,
[STACK_SYMBOL]: stack,
[EXPLICIT_SEVERITY]: explicitSeverity,
}),
);
return extensions;
},
[],
Expand Down Expand Up @@ -271,6 +280,9 @@ export class Ruleset {
if (extendedRuleset === this) continue;
for (const rule of Object.values(extendedRuleset.rules)) {
rules[rule.name] = rule;
if (this.#context[STACK_SYMBOL] !== void 0 && this.#context[EXPLICIT_SEVERITY] === true) {
rule.enabled = Rule.isEnabled(rule, this.#context.severity);
}
}
}
}
Expand All @@ -281,8 +293,7 @@ export class Ruleset {
rules[name] = rule;

if (rule.owner === this) {
rule.enabled =
this.#context.severity === 'all' || (this.#context.severity === 'recommended' && rule.recommended);
rule.enabled = Rule.isEnabled(rule, this.#context.severity);
}

if (rule.formats !== null) {
Expand Down

0 comments on commit 378b4b8

Please sign in to comment.