Skip to content

Commit

Permalink
chore(specs): add eslint rule to avoid cross-references (#3413)
Browse files Browse the repository at this point in the history
  • Loading branch information
millotp authored Jul 25, 2024
1 parent 71c4f92 commit 81c15cf
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 32 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ module.exports = {
'automation-custom/out-of-line-all-of': 'error',
'automation-custom/out-of-line-any-of': 'error',
'automation-custom/valid-acl': 'error',
'automation-custom/ref-common': 'error',
},
},
],
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ jobs:
- name: Test scripts
run: yarn scripts:test

- name: Lint custom eslint plugin
run: yarn workspace eslint-plugin-automation-custom lint

- name: Test custom eslint plugin
run: yarn workspace eslint-plugin-automation-custom test

Expand Down
4 changes: 3 additions & 1 deletion eslint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"name": "eslint-plugin-automation-custom",
"version": "1.0.0",
"description": "Custom rules for eslint",
"main": "dist/index.js",
"main": "dist/src/index.js",
"files": [
"src/**.ts"
],
"scripts": {
"build": "rm -rf dist/ && tsc",
"lint": "eslint --ext=ts .",
"lint:fix": "eslint --ext=ts --fix .",
"test": "jest"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions eslint/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { endWithDot } from './rules/endWithDot';
import { noFinalDot } from './rules/noFinalDot';
import { noNewLine } from './rules/noNewLine';
import { createOutOfLineRule } from './rules/outOfLineRule';
import { refCommon } from './rules/refCommon';
import { singleQuoteRef } from './rules/singleQuoteRef';
import { validACL } from './rules/validACL';

Expand All @@ -15,6 +16,7 @@ const rules = {
'single-quote-ref': singleQuoteRef,
'valid-acl': validACL,
'no-new-line': noNewLine,
'ref-common': refCommon,
};

// Custom parser for ESLint, to read plain text file like mustache.
Expand Down
4 changes: 1 addition & 3 deletions eslint/src/rules/endWithDot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ export const endWithDot: Rule.RuleModule = {

return {
YAMLPair(node): void {
if (
!isPairWithKey(node, 'description')
) {
if (!isPairWithKey(node, 'description')) {
return;
}

Expand Down
73 changes: 73 additions & 0 deletions eslint/src/rules/refCommon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { Rule } from 'eslint';

import { isPairWithKey, isScalar } from '../utils';

const allSpecs = [
'abtesting',
'analytics',
'crawler',
'ingestion',
'insights',
'monitoring',
'personalization',
'query-suggestions',
'recommend',
'search',
'usage',
];

export const refCommon: Rule.RuleModule = {
meta: {
docs: {
description:
'the $ref must target the current spec, or the common spec. If you intended to use a model from an other spec, move it to the common folder',
},
messages: {
refCommon: '$ref to another spec',
},
},
create(context) {
if (!context.sourceCode.parserServices.isYAML) {
return {};
}

return {
YAMLPair(node): void {
if (!isPairWithKey(node, '$ref')) {
return;
}

if (!isScalar(node.value)) {
return;
}

let ref = node.value.value;
if (
typeof ref !== 'string' ||
ref.trim().startsWith('#/') ||
ref.startsWith('./')
) {
return;
}

const spec = context.filename.match(/specs\/([a-z-]+?)\//)?.[1];
if (!spec) {
return;
}
while (ref.startsWith(`../`)) {
ref = ref.slice(3);
}
if (
allSpecs.filter((s) => s !== spec).every((s) => !ref.startsWith(s))
) {
return;
}

context.report({
node: node as any,
messageId: 'refCommon',
});
},
};
},
};
2 changes: 1 addition & 1 deletion eslint/src/rules/validACL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ACLs = [
'usage',
'logs',
'setUnretrievableAttributes',
'admin'
'admin',
];

export const validACL: Rule.RuleModule = {
Expand Down
2 changes: 1 addition & 1 deletion eslint/tests/noNewLine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ multiple lines`,
`,
errors: [{ messageId: 'noNewLine' }],
output: `multiple new lines`,
}
},
],
});
53 changes: 31 additions & 22 deletions eslint/tests/outOfLineRule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ root:
inside:
type: string
enum: [bla, blabla]
`,
`,
errors: [{ messageId: 'enumNotOutOfLine' }],
},
{
Expand All @@ -60,33 +60,37 @@ root:
useIt:
$ref: '#/root/inside/deeper'
`,
`,
errors: [{ messageId: 'enumNotOutOfLine' }],
},
],
});


// oneOf should allow `type: 'null'`
ruleTester.run('out-of-line-oneOf-null', createOutOfLineRule({ property: 'oneOf' }), {
valid: [
`
ruleTester.run(
'out-of-line-oneOf-null',
createOutOfLineRule({ property: 'oneOf' }),
{
valid: [
`
simple:
oneOf:
- type: string
- type: 'null'
`,`
`,
`
obj:
type: object
properties:
name:
oneOf:
- type: string
- type: 'null'
`],
invalid: [
{
code: `
`,
],
invalid: [
{
code: `
simple:
type: object
properties:
Expand All @@ -95,21 +99,26 @@ simple:
- type: string
- type: null
`,
errors: [{ messageId: 'oneOfNotOutOfLine' }],
},
],
});
errors: [{ messageId: 'oneOfNotOutOfLine' }],
},
],
}
);

// allow enum to be nullable
ruleTester.run('out-of-line-enum-null', createOutOfLineRule({ property: 'enum' }), {
valid: [
`
ruleTester.run(
'out-of-line-enum-null',
createOutOfLineRule({ property: 'enum' }),
{
valid: [
`
simple:
oneOf:
- type: string
enum: [bla, blabla]
- type: 'null'
`],
invalid: [],
});

`,
],
invalid: [],
}
);
67 changes: 67 additions & 0 deletions eslint/tests/refCommon.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { RuleTester } from 'eslint';

import { refCommon } from '../src/rules/refCommon';

const ruleTester = new RuleTester({
parser: require.resolve('yaml-eslint-parser'),
});

ruleTester.run('ref-common', refCommon, {
valid: [
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
local:
$ref: '#/components/schemas/Example'
`,
},
{
filename: 'renamedRepo/specs/search/path/test.yml',
code: `
local:
$ref: '#/components/schemas/Example'
`,
},
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
sameFolder:
$ref: './schemas/Example'
`,
},
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
external:
$ref: '../../common/schemas/Example'
`,
},
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
external:
$ref: '../../../search/schemas/Example'
`,
},
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
nested:
type: object
properties:
nested:
$ref: '#/components/schemas/Example'
`,
},
],
invalid: [
{
filename: 'api-client-automation/specs/search/path/test.yml',
code: `
out:
$ref: '../../recommend/schemas/Example'
`,
errors: [{ messageId: 'refCommon' }],
},
],
});
2 changes: 1 addition & 1 deletion eslint/tests/validACL.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RuleTester } from 'eslint';
import { validACL } from '../src/rules/validACL';

import { validACL } from '../src/rules/validACL';

const ruleTester = new RuleTester({
parser: require.resolve('yaml-eslint-parser'),
Expand Down
1 change: 1 addition & 0 deletions eslint/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
},
"include": [
"src/**/*.ts",
"tests/**/*.ts"
],
"exclude": [
"node_modules",
Expand Down
6 changes: 3 additions & 3 deletions scripts/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export async function formatter(language: string, cwd: string): Promise<void> {
case 'go':
await run('goimports -w . && golangci-lint run --fix', { cwd, language });
break;
case 'javascript':
await run(`yarn eslint --ext=ts,json ${cwd} --fix --no-error-on-unmatched-pattern`);
break;
case 'java':
await run(
`find . -type f -name "*.java" | xargs java --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
Expand All @@ -42,6 +39,9 @@ export async function formatter(language: string, cwd: string): Promise<void> {
{ cwd, language },
);
break;
case 'javascript':
await run(`yarn eslint --ext=ts,json ${cwd} --fix --no-error-on-unmatched-pattern`);
break;
case 'kotlin':
await run(`./gradle/gradlew -p ${cwd} spotlessApply`, { language });
break;
Expand Down

0 comments on commit 81c15cf

Please sign in to comment.