Skip to content

Commit

Permalink
feat(eslint-plugin)!: [array-type] rework options (#654)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: changes config structure

```ts
type ArrayOption = 'array' | 'generic' | 'array-simple';
type Options = [
  {
    // default case for all arrays
    default: ArrayOption,
    // optional override for readonly arrays
    readonly?: ArrayOption,
  },
];
```

Fixes #635
  • Loading branch information
a-tarasyuk authored and bradzacher committed Jul 22, 2019
1 parent ef99f71 commit 1389393
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 114 deletions.
23 changes: 18 additions & 5 deletions packages/eslint-plugin/docs/rules/array-type.md
Expand Up @@ -8,13 +8,26 @@ This rule aims to standardise usage of array types within your codebase.

## Options

This rule accepts one option - a single string
```ts
type ArrayOption = 'array' | 'generic' | 'array-simple';
type Options = {
default: ArrayOption;
readonly?: ArrayOption;
};

const defaultOptions: Options = {
default: 'array',
};
```

The rule accepts an options object with the following properties:

- `default` - sets the array type expected for mutable cases.
- `readonly` - sets the array type expected for readonly arrays. If this is omitted, then the value for `default` will be used.

- `"array"` enforces use of `T[]` for all types `T`.
- `"generic"` enforces use of `Array<T>` for all types `T`.
- `"array-simple"` enforces use of `T[]` if `T` is a simple type.
Each property can be set to one of three strings: `'array' | 'generic' | 'array-simple'`.

Without providing an option, by default the rule will enforce `"array"`.
The default config will enforce that all mutable and readonly arrays use the `'array'` syntax.

### `"array"`

Expand Down
87 changes: 68 additions & 19 deletions packages/eslint-plugin/src/rules/array-type.ts
Expand Up @@ -73,13 +73,20 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean {
}

export type OptionString = 'array' | 'generic' | 'array-simple';
type Options = [OptionString];
type Options = [
{
default: OptionString;
readonly?: OptionString;
}
];
type MessageIds =
| 'errorStringGeneric'
| 'errorStringGenericSimple'
| 'errorStringArray'
| 'errorStringArraySimple';

const arrayOption = { enum: ['array', 'generic', 'array-simple'] };

export default util.createRule<Options, MessageIds>({
name: 'array-type',
meta: {
Expand All @@ -102,14 +109,32 @@ export default util.createRule<Options, MessageIds>({
},
schema: [
{
enum: ['array', 'generic', 'array-simple'],
type: 'object',
properties: {
default: arrayOption,
readonly: arrayOption,
},
},
],
},
defaultOptions: ['array'],
create(context, [option]) {
defaultOptions: [
{
default: 'array',
},
],
create(context, [options]) {
const sourceCode = context.getSourceCode();

const defaultOption = options.default;
const readonlyOption = options.readonly || defaultOption;

const isArraySimpleOption =
defaultOption === 'array-simple' && readonlyOption === 'array-simple';
const isArrayOption =
defaultOption === 'array' && readonlyOption === 'array';
const isGenericOption =
defaultOption === 'generic' && readonlyOption === 'generic';

/**
* Check if whitespace is needed before this node
* @param node the node to be evaluated.
Expand Down Expand Up @@ -143,22 +168,36 @@ export default util.createRule<Options, MessageIds>({
}

return {
TSArrayType(node) {
TSArrayType(node: TSESTree.TSArrayType) {
if (
option === 'array' ||
(option === 'array-simple' && isSimpleType(node.elementType))
isArrayOption ||
(isArraySimpleOption && isSimpleType(node.elementType))
) {
return;
}
const messageId =
option === 'generic'
? 'errorStringGeneric'
: 'errorStringGenericSimple';

const isReadonly =
node.parent &&
node.parent.type === AST_NODE_TYPES.TSTypeOperator &&
node.parent.operator === 'readonly';

const isReadonlyGeneric =
readonlyOption === 'generic' && defaultOption !== 'generic';

const isReadonlyArray =
readonlyOption !== 'generic' && defaultOption === 'generic';

if (
(isReadonlyGeneric && !isReadonly) ||
(isReadonlyArray && isReadonly)
) {
return;
}

const messageId =
defaultOption === 'generic'
? 'errorStringGeneric'
: 'errorStringGenericSimple';
const typeOpNode = isReadonly ? node.parent! : null;

context.report({
Expand Down Expand Up @@ -201,23 +240,32 @@ export default util.createRule<Options, MessageIds>({
},
});
},

TSTypeReference(node: TSESTree.TSTypeReference) {
if (
option === 'generic' ||
isGenericOption ||
node.typeName.type !== AST_NODE_TYPES.Identifier
) {
return;
}
if (!['Array', 'ReadonlyArray'].includes(node.typeName.name)) {

const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray';
const isArrayType = node.typeName.name === 'Array';

if (
!(isArrayType || isReadonlyArrayType) ||
(readonlyOption === 'generic' && isReadonlyArrayType) ||
(defaultOption === 'generic' && !isReadonlyArrayType)
) {
return;
}

const messageId =
option === 'array' ? 'errorStringArray' : 'errorStringArraySimple';
const isReadonly = node.typeName.name === 'ReadonlyArray';
const readonlyPrefix = isReadonly ? 'readonly ' : '';

const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : '';
const typeParams = node.typeParameters && node.typeParameters.params;
const messageId =
defaultOption === 'array'
? 'errorStringArray'
: 'errorStringArraySimple';

if (!typeParams || typeParams.length === 0) {
// Create an 'any' array
Expand All @@ -231,12 +279,13 @@ export default util.createRule<Options, MessageIds>({
return fixer.replaceText(node, `${readonlyPrefix}any[]`);
},
});

return;
}

if (
typeParams.length !== 1 ||
(option === 'array-simple' && !isSimpleType(typeParams[0]))
(defaultOption === 'array-simple' && !isSimpleType(typeParams[0]))
) {
return;
}
Expand Down

0 comments on commit 1389393

Please sign in to comment.