-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(config): linter to detect invalid config property type (#1089)
## Proposed change Linter to detect invalid config property type. Every property in [StrictConfiguration](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40o3r/core/src/core/interfaces/configuration.ts#L27) can be for example `string` or `number` but not the both at the same time. It cannot be check with TypeScript, so I propose a linter for that.
- Loading branch information
Showing
6 changed files
with
255 additions
and
14 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
docs/linter/eslint-plugin/rules/no-multiple-type-configuration-property.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# @o3r/no-multiple-type-configuration-property | ||
|
||
Ensures that the configuration property does not accept multiple types. | ||
|
||
## How to use | ||
|
||
```json | ||
{ | ||
"@o3r/no-multiple-type-configuration-property": [ | ||
"error", | ||
{ | ||
"supportedInterfaceNames": ["NestedConfiguration", "Configuration", "CustomConfigurationInterface"] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
## Valid code example | ||
|
||
```typescript | ||
export interface MyFirstConfig extends Configuration { | ||
myProp: string; | ||
} | ||
``` | ||
|
||
## Invalid code example | ||
|
||
```typescript | ||
export interface MyConfig extends Configuration { | ||
myProp: string | number; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...t/no-multiple-type-configuration-property/no-multiple-type-configuration-property.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { TSESLint } from '@typescript-eslint/experimental-utils'; | ||
import noMultipleTypeConfigurationPropertyRule from './no-multiple-type-configuration-property'; | ||
|
||
const ruleTester = new TSESLint.RuleTester({ | ||
parser: require.resolve('@typescript-eslint/parser'), | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module' | ||
} | ||
}); | ||
|
||
const code = ` | ||
export interface Config extends Configuration { | ||
prop1: string; | ||
prop2: 'a' | 'b' | 'c'; | ||
prop3: 1 | 2 | 3; | ||
} | ||
`; | ||
|
||
ruleTester.run('no-multiple-type-configuration-property', noMultipleTypeConfigurationPropertyRule, { | ||
valid: [ | ||
{ code }, | ||
{ code: 'export interface A { prop1: string | number; }'} | ||
], | ||
invalid: [ | ||
{ | ||
code: code.replace(': string;', ': string | number | boolean;'), | ||
errors: [ | ||
{ | ||
suggestions: [ | ||
{ | ||
output: code, | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: 'string | number | boolean', | ||
recommendedValue: 'string' | ||
} | ||
}, | ||
{ | ||
output: code.replace(': string;', ': number;'), | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: 'string | number | boolean', | ||
recommendedValue: 'number' | ||
} | ||
}, | ||
{ | ||
output: code.replace(': string;', ': boolean;'), | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: 'string | number | boolean', | ||
recommendedValue: 'boolean' | ||
} | ||
} | ||
], | ||
messageId: 'error' | ||
} | ||
] | ||
}, | ||
{ | ||
code: code.replace(': string;', ': string & number;'), | ||
errors: [ | ||
{ | ||
suggestions: [ | ||
{ | ||
output: code, | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: 'string & number', | ||
recommendedValue: 'string' | ||
} | ||
}, | ||
{ | ||
output: code.replace(': string;', ': number;'), | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: 'string & number', | ||
recommendedValue: 'number' | ||
} | ||
} | ||
], | ||
messageId: 'error' | ||
} | ||
] | ||
}, | ||
{ | ||
code: code.replace(': string;', ': \'a\' | 1;'), | ||
errors: [ | ||
{ | ||
suggestions: [ | ||
{ | ||
output: code.replace(': string;', ': \'a\';'), | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: '\'a\' | 1', | ||
recommendedValue: '\'a\'' | ||
} | ||
}, | ||
{ | ||
output: code.replace(': string;', ': 1;'), | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: '\'a\' | 1', | ||
recommendedValue: '1' | ||
} | ||
} | ||
], | ||
messageId: 'error' | ||
} | ||
] | ||
} | ||
] | ||
}); |
79 changes: 79 additions & 0 deletions
79
...script/no-multiple-type-configuration-property/no-multiple-type-configuration-property.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { TSESTree } from '@typescript-eslint/experimental-utils'; | ||
import { createRule, defaultSupportedInterfaceNames, isExtendingConfiguration } from '../../utils'; | ||
|
||
const separatorRegExp = /\s*[|&]\s*/; | ||
|
||
export interface NoMultipleTypeConfigurationPropertyOption { | ||
supportedInterfaceNames?: string[]; | ||
} | ||
|
||
export default createRule<NoMultipleTypeConfigurationPropertyOption[], 'error' | 'suggestion'>({ | ||
name: 'no-multiple-type-configuration-property', | ||
meta: { | ||
hasSuggestions: true, | ||
type: 'problem', | ||
docs: { | ||
description: 'Ensures that the configuration property does not accept multiple types.', | ||
recommended: 'error' | ||
}, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
supportedInterfaceNames: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
}, | ||
default: defaultSupportedInterfaceNames | ||
} | ||
} | ||
} | ||
], | ||
messages: { | ||
error: 'Configuration cannot be the union of multiple type', | ||
suggestion: 'Replace {{currentValue}} by {{recommendedValue}}' | ||
} | ||
}, | ||
defaultOptions: [], | ||
create: (context) => { | ||
const supportedInterfaceNames = context.options.reduce((acc: string[], option) => acc.concat(option.supportedInterfaceNames || []), []); | ||
const sourceCode = context.getSourceCode(); | ||
|
||
const rule = (node: TSESTree.TSUnionType | TSESTree.TSIntersectionType) => { | ||
const interfaceDeclNode = node.parent?.parent?.parent?.parent; | ||
if (!isExtendingConfiguration(interfaceDeclNode, supportedInterfaceNames)) { | ||
return; // Not in a configuration interface | ||
} | ||
|
||
if ( | ||
node.types.every((type) => type.type === TSESTree.AST_NODE_TYPES.TSLiteralType && type.literal.type === TSESTree.AST_NODE_TYPES.Literal) | ||
&& [...(new Set((node.types as TSESTree.TSLiteralType[]).map((literalType) => typeof (literalType.literal as TSESTree.Literal).value)))].length === 1 | ||
) { | ||
return; // Only the same literal type | ||
} | ||
|
||
const text = sourceCode.getText(node); | ||
context.report({ | ||
messageId: 'error', | ||
node, | ||
loc: node.loc, | ||
suggest: text.split(separatorRegExp).map((type) => ({ | ||
messageId: 'suggestion', | ||
data: { | ||
currentValue: text, | ||
recommendedValue: type | ||
}, | ||
fix: (fixer) => fixer.replaceTextRange(node.range, type) | ||
})) | ||
}); | ||
}; | ||
|
||
return { | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
TSUnionType: rule, | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
TSIntersectionType: rule | ||
}; | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters