Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin)!: [array-type] add "default", "readonly" options #654

Merged
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