Skip to content

Commit c7f67bb

Browse files
cussiolmarschattha
andauthored
feat(plugins): add spectral linter plugin (#2705)
Add support for [Spectral](https://github.com/stoplightio/spectral) linter. Spectral is used as an analysis tool for YAML and JSON with built-in support for OpenAPI, AsyncAPI and Arazzo specifications. - [x] Plugin tests passed with snapshot created (`npm test spectral`). - [x] Manual testing with a project using Spectral. --------- Co-authored-by: Arslan <49312804+marschattha@users.noreply.github.com>
1 parent 7f100df commit c7f67bb

File tree

6 files changed

+298
-2
lines changed

6 files changed

+298
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ qlty plugins enable <NAME>
160160
| Kotlin | [complexity](https://github.com/qltysh/qlty/tree/main/qlty-smells), [duplication](https://github.com/qltysh/qlty/tree/main/qlty-smells), [osv-scanner](https://google.github.io/osv-scanner/), [radarlint](https://github.com/qltysh/radarlint), [trivy](https://trivy.dev) |
161161
| Kubernetes | [kube-linter](https://docs.kubelinter.io/#/) |
162162
| Markdown | [markdownlint](https://github.com/DavidAnson/markdownlint), [prettier](https://prettier.io/) |
163-
| OpenAPI | [redocly](https://redocly.com/docs/cli) |
164-
| PHP | [complexity](https://github.com/qltysh/qlty/tree/main/qlty-smells), [duplication](https://github.com/qltysh/qlty/tree/main/qlty-smells), [osv-scanner](https://google.github.io/osv-scanner/), [php-codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer), [php-cs-fixer](https://cs.symfony.com/), [phpstan](https://phpstan.org/), [radarlint](https://github.com/qltysh/radarlint), [trivy](https://trivy.dev) |
163+
| OpenAPI | [redocly](https://redocly.com/docs/cli), [spectral](https://meta.stoplight.io/docs/spectral) |
164+
| PHP | [complexity](https://github.com/qltysh/qlty/tree/main/qlty-smells), [duplication](https://github.com/qltysh/qlty/tree/main/qlty-smells), [osv-scanner](https://google.github.io/osv-scanner/), [php-codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer), [php-cs-fixer](https://cs.symfony.com/), [phpstan](https://phpstan.org/), [radarlint](https://github.com/qltysh/radarlint), [trivy](https://trivy.dev) |
165165
| Prisma | [prisma](https://github.com/prisma/prisma) |
166166
| Python | [bandit](https://bandit.readthedocs.io/en/latest/), [black](https://github.com/psf/black), [complexity](https://github.com/qltysh/qlty/tree/main/qlty-smells), [duplication](https://github.com/qltysh/qlty/tree/main/qlty-smells), [flake8](https://flake8.pycqa.org/en/latest/), [mypy](https://www.mypy-lang.org/), [osv-scanner](https://google.github.io/osv-scanner/), [radarlint](https://github.com/qltysh/radarlint), [ruff](https://docs.astral.sh/ruff/), [trivy](https://trivy.dev) |
167167
| R | [osv-scanner](https://google.github.io/osv-scanner/), [trivy](https://trivy.dev) |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
extends:
2+
- spectral:oas
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`linter=spectral fixture=openapi version=6.15.0 1`] = `
4+
{
5+
"issues": [
6+
{
7+
"category": "CATEGORY_LINT",
8+
"level": "LEVEL_MEDIUM",
9+
"location": {
10+
"path": "openapi.in.yaml",
11+
"range": {
12+
"endColumn": 61,
13+
"endLine": 13,
14+
"startColumn": 1,
15+
"startLine": 1,
16+
},
17+
},
18+
"message": "Info object must have "contact" object.",
19+
"mode": "MODE_COMMENT",
20+
"ruleKey": "info-contact",
21+
"snippet": "openapi: 3.0.0
22+
components:
23+
requestBodies:
24+
TestRequestBody:
25+
content:
26+
application/json:
27+
schema:
28+
type: object
29+
schemas:
30+
TestSchema:
31+
title: TestSchema
32+
allOf:
33+
- $ref: '#/components/requestBodies/TestRequestBody'",
34+
"snippetWithContext": "openapi: 3.0.0
35+
components:
36+
requestBodies:
37+
TestRequestBody:
38+
content:
39+
application/json:
40+
schema:
41+
type: object
42+
schemas:
43+
TestSchema:
44+
title: TestSchema
45+
allOf:
46+
- $ref: '#/components/requestBodies/TestRequestBody'",
47+
"tool": "spectral",
48+
},
49+
{
50+
"category": "CATEGORY_LINT",
51+
"level": "LEVEL_MEDIUM",
52+
"location": {
53+
"path": "openapi.in.yaml",
54+
"range": {
55+
"endColumn": 61,
56+
"endLine": 13,
57+
"startColumn": 1,
58+
"startLine": 1,
59+
},
60+
},
61+
"message": "Info "description" must be present and non-empty string.",
62+
"mode": "MODE_COMMENT",
63+
"ruleKey": "info-description",
64+
"snippet": "openapi: 3.0.0
65+
components:
66+
requestBodies:
67+
TestRequestBody:
68+
content:
69+
application/json:
70+
schema:
71+
type: object
72+
schemas:
73+
TestSchema:
74+
title: TestSchema
75+
allOf:
76+
- $ref: '#/components/requestBodies/TestRequestBody'",
77+
"snippetWithContext": "openapi: 3.0.0
78+
components:
79+
requestBodies:
80+
TestRequestBody:
81+
content:
82+
application/json:
83+
schema:
84+
type: object
85+
schemas:
86+
TestSchema:
87+
title: TestSchema
88+
allOf:
89+
- $ref: '#/components/requestBodies/TestRequestBody'",
90+
"tool": "spectral",
91+
},
92+
{
93+
"category": "CATEGORY_LINT",
94+
"level": "LEVEL_MEDIUM",
95+
"location": {
96+
"path": "openapi.in.yaml",
97+
"range": {
98+
"endColumn": 61,
99+
"endLine": 13,
100+
"startColumn": 1,
101+
"startLine": 1,
102+
},
103+
},
104+
"message": "OpenAPI "servers" must be present and non-empty array.",
105+
"mode": "MODE_COMMENT",
106+
"ruleKey": "oas3-api-servers",
107+
"snippet": "openapi: 3.0.0
108+
components:
109+
requestBodies:
110+
TestRequestBody:
111+
content:
112+
application/json:
113+
schema:
114+
type: object
115+
schemas:
116+
TestSchema:
117+
title: TestSchema
118+
allOf:
119+
- $ref: '#/components/requestBodies/TestRequestBody'",
120+
"snippetWithContext": "openapi: 3.0.0
121+
components:
122+
requestBodies:
123+
TestRequestBody:
124+
content:
125+
application/json:
126+
schema:
127+
type: object
128+
schemas:
129+
TestSchema:
130+
title: TestSchema
131+
allOf:
132+
- $ref: '#/components/requestBodies/TestRequestBody'",
133+
"tool": "spectral",
134+
},
135+
{
136+
"category": "CATEGORY_LINT",
137+
"level": "LEVEL_HIGH",
138+
"location": {
139+
"path": "openapi.in.yaml",
140+
"range": {
141+
"endColumn": 61,
142+
"endLine": 13,
143+
"startColumn": 1,
144+
"startLine": 1,
145+
},
146+
},
147+
"message": "must have required property "info".",
148+
"mode": "MODE_COMMENT",
149+
"ruleKey": "oas3-schema",
150+
"snippet": "openapi: 3.0.0
151+
components:
152+
requestBodies:
153+
TestRequestBody:
154+
content:
155+
application/json:
156+
schema:
157+
type: object
158+
schemas:
159+
TestSchema:
160+
title: TestSchema
161+
allOf:
162+
- $ref: '#/components/requestBodies/TestRequestBody'",
163+
"snippetWithContext": "openapi: 3.0.0
164+
components:
165+
requestBodies:
166+
TestRequestBody:
167+
content:
168+
application/json:
169+
schema:
170+
type: object
171+
schemas:
172+
TestSchema:
173+
title: TestSchema
174+
allOf:
175+
- $ref: '#/components/requestBodies/TestRequestBody'",
176+
"tool": "spectral",
177+
},
178+
{
179+
"category": "CATEGORY_LINT",
180+
"level": "LEVEL_HIGH",
181+
"location": {
182+
"path": "openapi.in.yaml",
183+
"range": {
184+
"endColumn": 61,
185+
"endLine": 13,
186+
"startColumn": 1,
187+
"startLine": 1,
188+
},
189+
},
190+
"message": "must have required property "paths".",
191+
"mode": "MODE_COMMENT",
192+
"ruleKey": "oas3-schema",
193+
"snippet": "openapi: 3.0.0
194+
components:
195+
requestBodies:
196+
TestRequestBody:
197+
content:
198+
application/json:
199+
schema:
200+
type: object
201+
schemas:
202+
TestSchema:
203+
title: TestSchema
204+
allOf:
205+
- $ref: '#/components/requestBodies/TestRequestBody'",
206+
"snippetWithContext": "openapi: 3.0.0
207+
components:
208+
requestBodies:
209+
TestRequestBody:
210+
content:
211+
application/json:
212+
schema:
213+
type: object
214+
schemas:
215+
TestSchema:
216+
title: TestSchema
217+
allOf:
218+
- $ref: '#/components/requestBodies/TestRequestBody'",
219+
"tool": "spectral",
220+
},
221+
{
222+
"category": "CATEGORY_LINT",
223+
"level": "LEVEL_MEDIUM",
224+
"location": {
225+
"path": "openapi.in.yaml",
226+
"range": {
227+
"endColumn": 61,
228+
"endLine": 13,
229+
"startColumn": 16,
230+
"startLine": 10,
231+
},
232+
},
233+
"message": "Potentially unused component has been detected.",
234+
"mode": "MODE_COMMENT",
235+
"ruleKey": "oas3-unused-component",
236+
"snippet": " TestSchema:
237+
title: TestSchema
238+
allOf:
239+
- $ref: '#/components/requestBodies/TestRequestBody'",
240+
"snippetWithContext": "openapi: 3.0.0
241+
components:
242+
requestBodies:
243+
TestRequestBody:
244+
content:
245+
application/json:
246+
schema:
247+
type: object
248+
schemas:
249+
TestSchema:
250+
title: TestSchema
251+
allOf:
252+
- $ref: '#/components/requestBodies/TestRequestBody'",
253+
"tool": "spectral",
254+
},
255+
],
256+
}
257+
`;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
openapi: 3.0.0
2+
components:
3+
requestBodies:
4+
TestRequestBody:
5+
content:
6+
application/json:
7+
schema:
8+
type: object
9+
schemas:
10+
TestSchema:
11+
title: TestSchema
12+
allOf:
13+
- $ref: '#/components/requestBodies/TestRequestBody'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
config_version = "0"
2+
3+
[plugins.definitions.spectral]
4+
runtime = "node"
5+
package = "@stoplight/spectral-cli"
6+
file_types = ["openapi"]
7+
config_files = [".spectral.yml", ".spectral.yaml", ".spectral.json", ".spectral.js"]
8+
latest_version = "6.15.0"
9+
known_good_version = "6.15.0"
10+
version_command = "spectral --version"
11+
description = "Static analysis tool for OpenAPI, AsyncAPI and Arazzo specifications"
12+
suggested_mode = "comment"
13+
14+
[plugins.definitions.spectral.drivers.lint]
15+
script = "spectral lint --format=github-actions ${target}"
16+
success_codes = [0, 1]
17+
output = "stdout"
18+
output_format = "regex"
19+
output_regex = '::(?P<severity>[^ ]+) title=(?P<code>[^,]+),file=(?P<path>[^,]+),col=(?P<col>\d+),endColumn=(?P<end_col>\d+),line=(?P<line>\d+),endLine=(?P<end_line>\d+)::(?P<message>.+)'
20+
cache_results = true
21+
output_missing = "parse"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { linterCheckTest } from "tests";
2+
3+
linterCheckTest("spectral", __dirname);

0 commit comments

Comments
 (0)