Skip to content

Commit 0ad6dd8

Browse files
committed
Merge branch 'develop' into feat/rule-extensions
2 parents 8c57a7b + a0294d9 commit 0ad6dd8

File tree

11 files changed

+82
-19
lines changed

11 files changed

+82
-19
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,20 @@ If you have a bug or feature request, please [create an issue](https://github.co
8484

8585
## 🌎 Real-World Rulesets
8686

87+
Stoplight has a set of Spectral rulesets that were created to help users get started with Stoplight's Style Guides. You can find them on [API Stylebook](https://apistylebook.stoplight.io/), and you can download the source Spectral file by selecting a style guide on the project sidebar and selecting **Export** -> **Spectral File(s)** on the top-right. A few noteworthy style guides are:
88+
89+
- [OWASP Top 10](https://apistylebook.stoplight.io/docs/owasp-top-10) - Set of rules to enforce [OWASP security guidelines](https://owasp.org/www-project-api-security/).
90+
- [URL Style Guidelines](https://apistylebook.stoplight.io/docs/url-guidelines) - Set of rules to help developers make better and consistent endpoints.
91+
92+
There are also rulesets created by many companies to improve their APIs. You can use these as is to lint your OpenAPI descriptions, or use these as a reference to learn more about what rules you would want in your own ruleset:
93+
8794
- [Adidas](https://github.com/adidas/api-guidelines/blob/master/.spectral.yml) - Adidas were one of the first companies to release their API Style Guide in a written guide _and_ a Spectral ruleset. Lots of good rules to try in here.
8895
- [APIs You Won't Hate](https://github.com/apisyouwonthate/style-guide) - An opinionated collection of rules based on advice in the [APIs You Won't Hate](https://apisyouwonthate.com/) community.
8996
- [Azure](https://github.com/Azure/azure-api-style-guide/blob/main/spectral.yaml) - Ruleset and complimentary style guide for creating OpenAPI 2 or 3 definitions of Azure services.
9097
- [Box](https://github.com/box/box-openapi/blob/main/.spectral.yml) - Lots of [Custom Functions](https://meta.stoplight.io/docs/spectral/ZG9jOjI1MTkw-custom-functions) being used to enforce good practices that the Box API governance folks are interested in.
9198
- [DigitalOcean](https://github.com/digitalocean/openapi/blob/main/spectral/ruleset.yml) - Keeping their OpenAPI nice and tidy, enforcing use of `$ref` (probably to minimize conflicts), naming conventions for Operation IDs, and all sorts of other handy OpenAPI tips.
9299
- [Tranascom](https://github.com/transcom/mymove/blob/master/swagger-def/.spectral.yml) - Don't even think about using anything other than `application/json`.
100+
- [Zalando](https://apistylebook.stoplight.io/docs/zalando-restful-api-guidelines) - Based on [Zalando's RESTFUL API Guidelines](https://github.com/zalando/restful-api-guidelines), covers a wide-range of API topics such as versioning standards, property naming standards, the default format for request/response properties, and more.
93101

94102
Here are [more real-world examples](https://github.com/stoplightio/spectral-rulesets) of Spectral in action.
95103

packages/core/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [@stoplight/spectral-core-v1.15.2](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-core-v1.15.1...@stoplight/spectral-core-v1.15.2) (2022-11-22)
2+
3+
4+
### Bug Fixes
5+
6+
* **core:** improve deep ruleset inheritance ([#2326](https://github.com/stoplightio/spectral/issues/2326)) ([378b4b8](https://github.com/stoplightio/spectral/commit/378b4b89769635e8b45d5325c15cfa00881b70bd))
7+
18
# [@stoplight/spectral-core-v1.15.1](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-core-v1.15.0...@stoplight/spectral-core-v1.15.1) (2022-10-24)
29

310

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stoplight/spectral-core",
3-
"version": "1.15.1",
3+
"version": "1.15.2",
44
"sideEffects": false,
55
"homepage": "https://github.com/stoplightio/spectral",
66
"bugs": "https://github.com/stoplightio/spectral/issues",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { DiagnosticSeverity } from '@stoplight/types';
2+
import { truthy } from '@stoplight/spectral-functions';
3+
4+
const ruleset1 = {
5+
rules: {
6+
'custom-info-description': {
7+
message: 'API Description is missing',
8+
severity: DiagnosticSeverity.Error,
9+
given: '$.info',
10+
then: {
11+
field: 'description',
12+
function: truthy,
13+
},
14+
},
15+
},
16+
};
17+
18+
const ruleset2 = {
19+
extends: [ruleset1],
20+
rules: {},
21+
};
22+
23+
export default {
24+
extends: [[ruleset2, 'off']],
25+
rules: {},
26+
};

packages/core/src/ruleset/__tests__/ruleset.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ describe('Ruleset', () => {
9595
expect(getEnabledRules(rules)).toEqual(['overridable-rule']);
9696
});
9797

98+
it('given nested extends with severity set to off #2', async () => {
99+
const { rules } = await loadRuleset(import('./__fixtures__/severity/off-proxy2'));
100+
expect(Object.keys(rules)).toEqual(['custom-info-description']);
101+
102+
expect(getEnabledRules(rules)).toEqual([]);
103+
});
104+
98105
it('given nested extends with severity set to off and explicit override to error', async () => {
99106
const { rules } = await loadRuleset(import('./__fixtures__/severity/error'));
100107
expect(Object.keys(rules)).toEqual([

packages/core/src/ruleset/rule.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { HumanReadableDiagnosticSeverity, IRuleThen, RuleDefinition, String
1111
import { minimatch } from './utils/minimatch';
1212
import { Formats } from './formats';
1313
import { resolveAlias } from './alias';
14-
import type { Stringified } from './types';
14+
import type { Stringified, FileRulesetSeverityDefinition } from './types';
1515

1616
export interface IRule {
1717
description: string | null;
@@ -76,6 +76,10 @@ export class Rule implements IRule {
7676
this.#enabled = enabled;
7777
}
7878

79+
public static isEnabled(rule: IRule, severity: FileRulesetSeverityDefinition): boolean {
80+
return severity === 'all' || (severity === 'recommended' && rule.recommended);
81+
}
82+
7983
public getSeverityForSource(source: string, path: JsonPath): DiagnosticSeverity | -1 {
8084
if (this.overrides === void 0 || this.overrides.definition.size === 0) {
8185
return this.severity;

packages/core/src/ruleset/ruleset.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import { isSimpleAliasDefinition } from './utils/guards';
2020
import type { Stringified } from './types';
2121

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

2526
type RulesetContext = {
2627
readonly severity?: FileRulesetSeverityDefinition;
2728
readonly source?: string;
2829
readonly [STACK_SYMBOL]?: Map<RulesetDefinition, Ruleset>;
30+
readonly [EXPLICIT_SEVERITY]?: boolean;
2931
};
3032

3133
let SEED = 1;
@@ -110,8 +112,9 @@ export class Ruleset {
110112
(extensions, extension) => {
111113
let actualExtension;
112114
let severity: FileRulesetSeverityDefinition = 'recommended';
115+
const explicitSeverity = Array.isArray(extension);
113116

114-
if (Array.isArray(extension)) {
117+
if (explicitSeverity) {
115118
[actualExtension, severity] = extension;
116119
} else {
117120
actualExtension = extension;
@@ -123,7 +126,13 @@ export class Ruleset {
123126
return extensions;
124127
}
125128

126-
extensions.push(new Ruleset(actualExtension, { severity, [STACK_SYMBOL]: stack }));
129+
extensions.push(
130+
new Ruleset(actualExtension, {
131+
severity,
132+
[STACK_SYMBOL]: stack,
133+
[EXPLICIT_SEVERITY]: explicitSeverity,
134+
}),
135+
);
127136
return extensions;
128137
},
129138
[],
@@ -271,6 +280,9 @@ export class Ruleset {
271280
if (extendedRuleset === this) continue;
272281
for (const rule of Object.values(extendedRuleset.rules)) {
273282
rules[rule.name] = rule;
283+
if (this.#context[STACK_SYMBOL] !== void 0 && this.#context[EXPLICIT_SEVERITY] === true) {
284+
rule.enabled = Rule.isEnabled(rule, this.#context.severity);
285+
}
274286
}
275287
}
276288
}
@@ -281,8 +293,7 @@ export class Ruleset {
281293
rules[name] = rule;
282294

283295
if (rule.owner === this) {
284-
rule.enabled =
285-
this.#context.severity === 'all' || (this.#context.severity === 'recommended' && rule.recommended);
296+
rule.enabled = Rule.isEnabled(rule, this.#context.severity);
286297
}
287298

288299
if (rule.formats !== null) {

packages/ruleset-migrator/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [@stoplight/spectral-ruleset-migrator-v1.9.1](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-ruleset-migrator-v1.9.0...@stoplight/spectral-ruleset-migrator-v1.9.1) (2022-11-23)
2+
3+
4+
### Bug Fixes
5+
6+
* **ruleset-migrator:** avoid positive lookbehinds ([#2349](https://github.com/stoplightio/spectral/issues/2349)) ([455c324](https://github.com/stoplightio/spectral/commit/455c32487b6f25465c1204186006e2c830f48eb3))
7+
18
# [@stoplight/spectral-ruleset-migrator-v1.9.0](https://github.com/stoplightio/spectral/compare/@stoplight/spectral-ruleset-migrator-v1.8.0...@stoplight/spectral-ruleset-migrator-v1.9.0) (2022-10-24)
29

310

packages/ruleset-migrator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stoplight/spectral-ruleset-migrator",
3-
"version": "1.9.0",
3+
"version": "1.9.1",
44
"homepage": "https://github.com/stoplightio/spectral",
55
"bugs": "https://github.com/stoplightio/spectral/issues",
66
"author": "Stoplight <[email protected]>",

packages/ruleset-migrator/src/transformers/formats.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,11 @@ const FORMATS = [
2626

2727
function safeFormat(format: string): string {
2828
return format
29-
.replace(/\.|(?<=[0-9])-(?=[0-9])/g, '_')
29+
.replace(/\.|([0-9])-(?=[0-9])/g, '$1_')
3030
.replace(/-([0-9a-z])/g, (match, char) => String(char).toUpperCase());
3131
}
3232

33-
const REPLACEMENTS = Object.fromEntries(
34-
FORMATS.map(format => [
35-
format,
36-
(ALIASES[format] ?? format)
37-
.replace(/\.|(?<=[0-9])-(?=[0-9])/g, '_')
38-
.replace(/-([0-9a-z])/g, (match, char) => String(char).toUpperCase()),
39-
]),
40-
);
33+
const REPLACEMENTS = Object.fromEntries(FORMATS.map(format => [format, safeFormat(ALIASES[format] ?? format)]));
4134

4235
function transform(input: unknown, ctx: TransformerCtx): namedTypes.ArrayExpression {
4336
assertArray(input);

0 commit comments

Comments
 (0)