Skip to content

Commit 5fd1391

Browse files
authored
style(spec): add out-of-line-enum rule APIC-416 (#356)
1 parent 0edc896 commit 5fd1391

File tree

120 files changed

+1006
-660
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+1006
-660
lines changed

.eslintrc.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ module.exports = {
1313
parser: 'yaml-eslint-parser',
1414
plugins: ["automation-custom"],
1515
rules: {
16-
'@typescript-eslint/naming-convention': 0,
16+
'yml/plain-scalar': [
17+
2,
18+
"always"
19+
, {
20+
// ignore path from ref, that must be quoted
21+
ignorePatterns: [
22+
'[./#a-zA-Z0-9_]+'
23+
]
24+
}
25+
],
1726
'yml/quotes': [
1827
2,
1928
{
@@ -35,7 +44,16 @@ module.exports = {
3544
files: ['specs/**/*.yml'],
3645
rules: {
3746
"automation-custom/description-dot": "error",
38-
}
47+
"automation-custom/single-quote-ref": "error",
48+
},
49+
overrides: [
50+
{
51+
files: ['!specs/bundled/*.yml'],
52+
rules: {
53+
"automation-custom/out-of-line-enum": "error",
54+
}
55+
}
56+
]
3957
}
4058
]
4159
},

.github/.cache_version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.1.0
1+
7.2.0

eslint/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { descriptionDot } from './rules/descriptionDot';
2+
import { outOfLineEnum } from './rules/outOfLineEnum';
3+
import { singleQuoteRef } from './rules/singleQuoteRef';
24

35
const rules = {
46
'description-dot': descriptionDot,
7+
'out-of-line-enum': outOfLineEnum,
8+
'single-quote-ref': singleQuoteRef,
59
};
610

711
export { rules };

eslint/src/rules/outOfLineEnum.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { Rule } from 'eslint';
2+
3+
import { isPairWithKey } from '../utils';
4+
5+
export const outOfLineEnum: Rule.RuleModule = {
6+
meta: {
7+
docs: {
8+
description: 'enum must be out of line',
9+
},
10+
messages: {
11+
enumNotOutOfLine: 'enum is not out of line',
12+
},
13+
},
14+
create(context) {
15+
if (!context.parserServices.isYAML) {
16+
return {};
17+
}
18+
19+
return {
20+
YAMLPair(node): void {
21+
if (!isPairWithKey(node, 'enum')) {
22+
return;
23+
}
24+
// parent is mapping, and parent is real parent that must be to the far left
25+
if (node.parent.parent.loc.start.column === 0) {
26+
return;
27+
}
28+
if (
29+
isPairWithKey(
30+
node.parent.parent.parent.parent?.parent?.parent?.parent ?? null,
31+
'servers'
32+
)
33+
) {
34+
// accept out of line enum if in servers
35+
return;
36+
}
37+
context.report({
38+
node: node.parent.parent as any,
39+
messageId: 'enumNotOutOfLine',
40+
});
41+
},
42+
};
43+
},
44+
};

eslint/src/rules/singleQuoteRef.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Rule } from 'eslint';
2+
3+
import { isBLockScalar, isPairWithKey, isScalar } from '../utils';
4+
5+
export const singleQuoteRef: Rule.RuleModule = {
6+
meta: {
7+
docs: {
8+
description: '$ref must be wrapped in single quote',
9+
},
10+
messages: {
11+
refNoQuote: '$ref is not wrapped in single quote',
12+
},
13+
fixable: 'code',
14+
},
15+
create(context) {
16+
if (!context.parserServices.isYAML) {
17+
return {};
18+
}
19+
20+
return {
21+
YAMLPair(node): void {
22+
if (!isPairWithKey(node, '$ref')) {
23+
return;
24+
}
25+
if (!isScalar(node.value)) {
26+
// not our problem, something else will fail like path resolution
27+
return;
28+
}
29+
if (node.value.style === 'single-quoted') {
30+
// that's what we want
31+
return;
32+
}
33+
if (isBLockScalar(node.value)) {
34+
// another rule should take care of that case
35+
return;
36+
}
37+
const hasDoubleQuote = node.value.style === 'double-quoted';
38+
const [start, end] = node.value.range;
39+
context.report({
40+
node: node as any,
41+
messageId: 'refNoQuote',
42+
*fix(fixer) {
43+
if (hasDoubleQuote) {
44+
yield fixer.replaceTextRange([start, start + 1], "'");
45+
yield fixer.replaceTextRange([end - 1, end], "'");
46+
} else {
47+
yield fixer.insertTextBeforeRange([start, start], "'");
48+
yield fixer.insertTextAfterRange([end, end], "'");
49+
}
50+
},
51+
});
52+
},
53+
};
54+
},
55+
};

eslint/tests/outOfLineEnum.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { RuleTester } from 'eslint';
2+
3+
import { outOfLineEnum } from '../src/rules/outOfLineEnum';
4+
5+
const ruleTester = new RuleTester({
6+
parser: require.resolve('yaml-eslint-parser'),
7+
});
8+
9+
ruleTester.run('out-of-line-enum', outOfLineEnum, {
10+
valid: [
11+
`
12+
simple:
13+
type: string
14+
enum: [bla, blabla]
15+
`,
16+
`
17+
simple:
18+
type: string
19+
enum:
20+
- bla
21+
- blabla
22+
`,
23+
`
24+
simple:
25+
type: string
26+
enum: [bla, blabla]
27+
28+
useIt:
29+
$ref: '#/simple'
30+
`,
31+
`
32+
servers:
33+
- url: http://test-server.com
34+
variables:
35+
region:
36+
default: us
37+
enum:
38+
- us
39+
- de
40+
`,
41+
],
42+
invalid: [
43+
{
44+
code: `
45+
root:
46+
inside:
47+
type: string
48+
enum: [bla, blabla]
49+
`,
50+
errors: [{ messageId: 'enumNotOutOfLine' }],
51+
},
52+
{
53+
code: `
54+
root:
55+
inside:
56+
deeper:
57+
type: string
58+
enum: [bla, blabla]
59+
60+
useIt:
61+
$ref: '#/root/inside/deeper'
62+
`,
63+
errors: [{ messageId: 'enumNotOutOfLine' }],
64+
},
65+
],
66+
});

eslint/tests/singleQuoteRef.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { RuleTester } from 'eslint';
2+
3+
import { singleQuoteRef } from '../src/rules/singleQuoteRef';
4+
5+
const ruleTester = new RuleTester({
6+
parser: require.resolve('yaml-eslint-parser'),
7+
});
8+
9+
ruleTester.run('single-quote-ref', singleQuoteRef, {
10+
valid: [
11+
`
12+
simple:
13+
$ref: 'random/path.yml'`,
14+
`
15+
sameFile:
16+
$ref: '#/inside'`,
17+
`
18+
long:
19+
$ref: 'some/random/file.yml#/root/object/prop'
20+
`,
21+
`
22+
post:
23+
description: test desc.
24+
'400':
25+
$ref: '../../common/responses/BadRequest.yml'
26+
`,
27+
],
28+
invalid: [
29+
{
30+
code: `
31+
simple:
32+
$ref: random/path.yml
33+
`,
34+
errors: [{ messageId: 'refNoQuote' }],
35+
output: `
36+
simple:
37+
$ref: 'random/path.yml'
38+
`,
39+
},
40+
{
41+
code: `
42+
long:
43+
$ref: some/random/file.yml#/root/object/prop
44+
`,
45+
errors: [{ messageId: 'refNoQuote' }],
46+
output: `
47+
long:
48+
$ref: 'some/random/file.yml#/root/object/prop'
49+
`,
50+
},
51+
{
52+
code: `
53+
post:
54+
description: test desc.
55+
'400':
56+
$ref: ../../common/responses/BadRequest.yml
57+
'404':
58+
$ref: ../../common/responses/IndexNotFound.yml
59+
`,
60+
errors: [{ messageId: 'refNoQuote' }, { messageId: 'refNoQuote' }],
61+
output: `
62+
post:
63+
description: test desc.
64+
'400':
65+
$ref: '../../common/responses/BadRequest.yml'
66+
'404':
67+
$ref: '../../common/responses/IndexNotFound.yml'
68+
`,
69+
},
70+
{
71+
code: `
72+
double:
73+
$ref: "some/ref"
74+
`,
75+
errors: [{ messageId: 'refNoQuote' }],
76+
output: `
77+
double:
78+
$ref: 'some/ref'
79+
`,
80+
},
81+
],
82+
});

scripts/buildSpecs.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,39 @@ async function propagateTagsToOperations(
4141
return true;
4242
}
4343

44+
async function lintCommon(verbose: boolean, useCache: boolean): Promise<void> {
45+
let hash = '';
46+
const cacheFile = toAbsolutePath(`specs/dist/common.cache`);
47+
if (useCache) {
48+
const { cacheExists, hash: newCache } = await checkForCache(
49+
{
50+
job: 'common specs',
51+
folder: toAbsolutePath('specs/'),
52+
generatedFiles: [],
53+
filesToCache: ['common'],
54+
cacheFile,
55+
},
56+
verbose
57+
);
58+
59+
if (cacheExists) {
60+
return;
61+
}
62+
63+
hash = newCache;
64+
}
65+
66+
const spinner = createSpinner('linting common spec', verbose).start();
67+
await run(`yarn specs:lint common`, { verbose });
68+
69+
if (hash) {
70+
spinner.text = `storing common spec cache`;
71+
await fsp.writeFile(cacheFile, hash);
72+
}
73+
74+
spinner.succeed();
75+
}
76+
4477
async function buildSpec(
4578
client: string,
4679
outputFormat: string,
@@ -87,7 +120,7 @@ async function buildSpec(
87120
}
88121

89122
spinner.text = `linting ${client} spec`;
90-
await run(`yarn specs:lint ${client}`, { verbose });
123+
await run(`yarn specs:fix ${client}`, { verbose });
91124

92125
spinner.text = `validating ${client} spec`;
93126
await run(`yarn openapi lint specs/bundled/${client}.${outputFormat}`, {
@@ -113,6 +146,8 @@ export async function buildSpecs(
113146
): Promise<void> {
114147
await fsp.mkdir(toAbsolutePath('specs/dist'), { recursive: true });
115148

149+
await lintCommon(verbose, useCache);
150+
116151
await Promise.all(
117152
clients.map((client) => buildSpec(client, outputFormat, verbose, useCache))
118153
);

specs/abtesting/common/schemas/ABTest.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ABTest:
99
additionalProperties: false
1010
properties:
1111
abTestID:
12-
$ref: ../parameters.yml#/abTestID
12+
$ref: '../parameters.yml#/abTestID'
1313
clickSignificance:
1414
type: number
1515
format: double
@@ -19,16 +19,16 @@ ABTest:
1919
format: double
2020
description: A/B test significance based on conversion data. Should be > 0.95 to be considered significant (no matter which variant is winning).
2121
endAt:
22-
$ref: ../parameters.yml#/endAt
22+
$ref: '../parameters.yml#/endAt'
2323
createdAt:
24-
$ref: ../parameters.yml#/createdAt
24+
$ref: '../parameters.yml#/createdAt'
2525
name:
26-
$ref: ../parameters.yml#/name
26+
$ref: '../parameters.yml#/name'
2727
status:
2828
type: string
2929
description: status of the A/B test.
3030
variants:
31-
$ref: Variant.yml#/variants
31+
$ref: 'Variant.yml#/variants'
3232
required:
3333
- status
3434
- name

0 commit comments

Comments
 (0)