Skip to content

Commit

Permalink
fix(specs): proper title with linter (#3444)
Browse files Browse the repository at this point in the history
  • Loading branch information
millotp authored Jul 30, 2024
1 parent 3e90919 commit e179701
Show file tree
Hide file tree
Showing 74 changed files with 313 additions and 94 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ module.exports = {
'automation-custom/out-of-line-any-of': 'error',
'automation-custom/valid-acl': 'error',
'automation-custom/ref-common': 'error',
'automation-custom/valid-inline-title': 'error',
},
},
],
Expand Down
2 changes: 2 additions & 0 deletions eslint/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createOutOfLineRule } from './rules/outOfLineRule';
import { refCommon } from './rules/refCommon';
import { singleQuoteRef } from './rules/singleQuoteRef';
import { validACL } from './rules/validACL';
import { validInlineTitle } from './rules/validInlineTitle';

const rules = {
'end-with-dot': endWithDot,
Expand All @@ -17,6 +18,7 @@ const rules = {
'valid-acl': validACL,
'no-new-line': noNewLine,
'ref-common': refCommon,
'valid-inline-title': validInlineTitle,
};

// Custom parser for ESLint, to read plain text file like mustache.
Expand Down
23 changes: 1 addition & 22 deletions eslint/src/rules/outOfLineRule.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { Rule } from 'eslint';
import type { AST } from 'yaml-eslint-parser';

import {
isBlockScalar,
isMapping,
isPairWithKey,
isScalar,
isSequence,
} from '../utils';
import { isNullable, isPairWithKey } from '../utils';

export function createOutOfLineRule({
property,
Expand Down Expand Up @@ -74,17 +67,3 @@ export function createOutOfLineRule({
};
return rule;
}

function isNullable(node: AST.YAMLNode | null): boolean {
return (
isSequence(node) &&
node.entries.some(
(entry) =>
isMapping(entry) &&
isPairWithKey(entry.pairs[0], 'type') &&
isScalar(entry.pairs[0].value) &&
!isBlockScalar(entry.pairs[0].value) &&
entry.pairs[0].value.raw === "'null'"
)
);
}
103 changes: 103 additions & 0 deletions eslint/src/rules/validInlineTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { Rule } from 'eslint';

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

export const validInlineTitle: Rule.RuleModule = {
meta: {
docs: {
description:
'title must be set in inline models, should be the first property and start with a lowercase',
},
messages: {
inlineTitleExists: 'title must be set in inline models',
lowercaseTitle: 'title must start with a lowercase',
firstProperty: 'title must be the first property',
noSpaceInTitle: 'title must not contain spaces',
},
},
create(context) {
if (!context.sourceCode.parserServices.isYAML) {
return {};
}

return {
YAMLPair(node): void {
if (
!isPairWithKey(node, 'type') ||
node.value?.type !== 'YAMLScalar' ||
node.value.value !== 'object'
) {
return;
}

// we don't enforce it for root level object
if (node.parent.parent.loc.start.column === 0) {
return;
}

// make sure title starts with a lowercase
const title = node.parent.pairs.find((pair) =>
isPairWithKey(pair, 'title')
);
const titleNode = title?.value;
const titleValue = (titleNode as any)?.value as string;
if (
titleNode &&
(titleNode.type !== 'YAMLScalar' || !/^[a-z]/.test(titleValue))
) {
context.report({
node: title,
messageId: 'lowercaseTitle',
});
}

// make sure title doesn't contain spaces
if (titleValue?.includes(' ')) {
context.report({
node: title,
messageId: 'noSpaceInTitle',
});
}

// if there are no properties, we don't need a title
const properties = node.parent.pairs.find((pair) =>
isPairWithKey(pair, 'properties')
);
if (!properties) {
return;
}

// allow it on nullable objects
if (
isPairWithKey(node.parent.parent.parent, 'oneOf') &&
isNullable(node.parent.parent.parent.value)
) {
return;
}

// allow on allOf too, since they are not generated
if (isPairWithKey(node.parent.parent.parent, 'allOf')) {
return;
}

// make sure the title is set on the same object
if (!title) {
context.report({
node: node.value,
messageId: 'inlineTitleExists',
});

return;
}

// make sure title is the first property
if (!isPairWithKey(node.parent.pairs[0], 'title')) {
context.report({
node: title,
messageId: 'firstProperty',
});
}
},
};
},
};
14 changes: 14 additions & 0 deletions eslint/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,17 @@ export function isPairWithKey(
return false;
return isScalar(node.key) && node.key.value === key;
}

export function isNullable(node: AST.YAMLNode | null): boolean {
return (
isSequence(node) &&
node.entries.some(
(entry) =>
isMapping(entry) &&
isPairWithKey(entry.pairs[0], 'type') &&
isScalar(entry.pairs[0].value) &&
!isBlockScalar(entry.pairs[0].value) &&
entry.pairs[0].value.raw === "'null'"
)
);
}
95 changes: 95 additions & 0 deletions eslint/tests/validInlineTitle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { RuleTester } from 'eslint';

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

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

ruleTester.run('valid-inline-title', validInlineTitle, {
valid: [
`
currencies:
type: object
properties:
inner:
type: object
`,
`
currencies:
type: object
properties:
inner:
title: currency
type: object
properties:
currency:
type: string
title: Currency
`,
`
dictionaryLanguage:
oneOf:
- type: object
properties:
prop:
type: integer
- type: 'null'
`,
],
invalid: [
{
code: `
currencies:
type: object
properties:
inner:
type: object
properties:
currency:
type: string
title: Currency
`,
errors: [{ messageId: 'inlineTitleExists' }],
},
{
code: `
currencies:
type: object
properties:
inner:
type: object
title: currency
properties:
currency:
type: string
title: Currency
`,
errors: [{ messageId: 'firstProperty' }],
},
{
code: `
currencies:
title: UpperCaseFine
type: object
properties:
inner:
title: UpperCaseNotFine
type: object
`,
errors: [{ messageId: 'lowercaseTitle' }],
},
{
code: `
currencies:
title: spaces are fine
type: object
properties:
inner:
title: spaces are not fine
type: object
`,
errors: [{ messageId: 'noSpaceInTitle' }],
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class AlgoliaSwiftGenerator extends Swift5ClientCodegen {
"facetordering",
"facets",
"facetsstats",
"forbidden",
"highlightresult",
"highlightresultoption",
"ignoreplurals",
Expand All @@ -83,10 +84,11 @@ public class AlgoliaSwiftGenerator extends Swift5ClientCodegen {
"promoteobjectids",
"querysuggestionsconfiguration",
"querytype",
"range",
"rankinginfo",
"redirect",
"redirectruleindexmetadata",
"redirectruleindexmetadatadata",
"redirectruleindexdata",
"redirecturl",
"region",
"removestopwords",
Expand Down
2 changes: 1 addition & 1 deletion scripts/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function formatter(language: string, cwd: string): Promise<void> {
break;
case 'java':
await run(
`find . -type f -name "*.java" | xargs java --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
`find . -path ./.gradle -prune -o -type f -name "*.java" | xargs java --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
Expand Down
2 changes: 2 additions & 0 deletions specs/abtesting/common/parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ filterEffects:
description: A/B test filter effects resulting from configuration settings.
properties:
outliers:
title: outliersFilter
type: object
description: Outliers removed from the A/B test as a result of configuration settings.
example:
Expand All @@ -111,6 +112,7 @@ filterEffects:
description: Number of tracked searches removed from the A/B test.
example: 237
emptySearch:
title: emptySearchFilter
type: object
description: Empty searches removed from the A/B test as a result of configuration settings.
example:
Expand Down
3 changes: 3 additions & 0 deletions specs/analytics/common/parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ clickPositions:
minItems: 12
maxItems: 12
items:
title: clickPosition
type: object
description: Click position.
properties:
Expand Down Expand Up @@ -242,10 +243,12 @@ purchaseCount:
example: 10

currencies:
title: currenciesValue
type: object
description: Revenue associated with this search, broken-down by currencies.
default: {}
additionalProperties:
title: currencyCode
x-additionalPropertiesName: currency
type: object
description: Currency code.
Expand Down
6 changes: 3 additions & 3 deletions specs/analytics/common/schemas/getTopHits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ topHitsResponse:
type: array
description: Most frequent search results.
items:
type: object
title: topHit
type: object
additionalProperties: false
required:
- hit
Expand All @@ -32,8 +32,8 @@ topHitsResponseWithAnalytics:
type: array
description: Most frequent search results with click and conversion metrics.
items:
type: object
title: topHitWithAnalytics
type: object
additionalProperties: false
required:
- hit
Expand Down Expand Up @@ -70,8 +70,8 @@ topHitsResponseWithRevenueAnalytics:
type: array
description: Most frequent search results with click, conversion, and revenue metrics.
items:
type: object
title: topHitWithRevenueAnalytics
type: object
additionalProperties: false
required:
- hit
Expand Down
Loading

0 comments on commit e179701

Please sign in to comment.