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] detect Readonly<string[]> case #8752

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 3 additions & 1 deletion packages/eslint-plugin/docs/rules/array-type.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ const y: readonly string[] = ['a', 'b'];

### `"generic"`

Always use `Array<T>` or `ReadonlyArray<T>` for all array types.
Always use `Array<T>`, `ReadonlyArray<T>`, or `Readonly<T[]>` for all array types.
`readonly T[]` will be modified to `ReadonlyArray<T>`.

<Tabs>
<TabItem value="❌ Incorrect">
Expand All @@ -58,6 +59,7 @@ const y: readonly string[] = ['a', 'b'];
```ts option='{ "default": "generic" }'
const x: Array<string> = ['a', 'b'];
const y: ReadonlyArray<string> = ['a', 'b'];
const z: Readonly<string[]> = ['a', 'b'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re #8752 (comment)
(One of) the CI failure(s) is due to this line being flagged, despite being in the "Correct" tab. (you can try it for yourself in the deploy preview by clicking the "Open in playground" button)

 FAIL  tests/docs.test.ts (25.146 s, 827 MB heap size)
  ● Validating rule docs › array-type.mdx › code examples ESLint output
    assert(received)
    Expected value to be equal to:
      true
    Received:
      false
    
    Message:
      Expected not to contain lint errors:
    const x: Array<string> = ['a', 'b'];
    const y: ReadonlyArray<string> = ['a', 'b'];
    const z: Readonly<string[]> = ['a', 'b'];

Shoutout to @auvred's for his work in #8382 / https://github.com/typescript-eslint/typescript-eslint/pull/8497/files#diff-62842d0740c234c02c26a4ccd3b3fe920b872cff1479f0703c6ec44f9cfb2f3dR407 that caught this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for your advise!
스크린샷 2024-05-11 오후 11 22 13

but there is one problem. i use incorrect exampleconst z: Readonly<string[]> = ['a', 'b']; but lint cannot use this type. Is there no way?

```

</TabItem>
Expand Down
24 changes: 18 additions & 6 deletions packages/eslint-plugin/src/rules/array-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ type MessageIds =
| 'errorStringArray'
| 'errorStringArraySimple'
| 'errorStringGeneric'
| 'errorStringGenericSimple';
| 'errorStringGenericSimple'
| 'errorStringArrayReadonly';

export default createRule<Options, MessageIds>({
name: 'array-type',
Expand All @@ -99,6 +100,8 @@ export default createRule<Options, MessageIds>({
"Array type using '{{readonlyPrefix}}{{type}}[]' is forbidden. Use '{{className}}<{{type}}>' instead.",
errorStringArray:
"Array type using '{{className}}<{{type}}>' is forbidden. Use '{{readonlyPrefix}}{{type}}[]' instead.",
errorStringArrayReadonly:
"Array type using '{{className}}<{{type}}>' is forbidden. Use '{{readonlyPrefix}}{{type}}' instead.",
errorStringArraySimple:
"Array type using '{{className}}<{{type}}>' is forbidden for simple types. Use '{{readonlyPrefix}}{{type}}[]' instead.",
errorStringGenericSimple:
Expand Down Expand Up @@ -199,13 +202,20 @@ export default createRule<Options, MessageIds>({
node.typeName.type !== AST_NODE_TYPES.Identifier ||
!(
node.typeName.name === 'Array' ||
node.typeName.name === 'ReadonlyArray'
node.typeName.name === 'ReadonlyArray' ||
node.typeName.name === 'Readonly'
)
) {
return;
}

const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray';
const isReadonlyWithGenericArrayType =
node.typeName.name === 'Readonly' &&
node.typeArguments?.params[0].type === AST_NODE_TYPES.TSArrayType;
Comment on lines +214 to +216
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] If the type name is Readonly and first type argument is not an array, we should return from the function. Because otherwise any Readonly<...> type will be reported.

const x: Readonly<string> = 'a'

playground

const isReadonlyArrayType =
node.typeName.name === 'ReadonlyArray' ||
isReadonlyWithGenericArrayType;

const currentOption = isReadonlyArrayType
? readonlyOption
: defaultOption;
Expand All @@ -218,7 +228,9 @@ export default createRule<Options, MessageIds>({
const typeParams = node.typeArguments?.params;
const messageId =
currentOption === 'array'
? 'errorStringArray'
? isReadonlyWithGenericArrayType
? 'errorStringArrayReadonly'
: 'errorStringArray'
: 'errorStringArraySimple';

if (!typeParams || typeParams.length === 0) {
Expand Down Expand Up @@ -256,13 +268,13 @@ export default createRule<Options, MessageIds>({
const start = `${parentParens ? '(' : ''}${readonlyPrefix}${
typeParens ? '(' : ''
}`;
const end = `${typeParens ? ')' : ''}[]${parentParens ? ')' : ''}`;
const end = `${typeParens ? ')' : ''}${isReadonlyWithGenericArrayType ? '' : `[]`}${parentParens ? ')' : ''}`;

context.report({
node,
messageId,
data: {
className: isReadonlyArrayType ? 'ReadonlyArray' : 'Array',
className: isReadonlyArrayType ? node.typeName.name : 'Array',
readonlyPrefix,
type: getMessageType(type),
},
Expand Down
39 changes: 29 additions & 10 deletions packages/eslint-plugin/tests/rules/array-type.test.ts
developer-bandi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ function bazFunction(baz: Arr<ArrayClass<String>>) {
code: 'let a: readonly Array<number>[] = [[]];',
options: [{ default: 'generic', readonly: 'array' }],
},
{
code: 'let a: Readonly = [];',
options: [{ default: 'generic', readonly: 'array' }],
},
],
invalid: [
// Base cases from https://github.com/typescript-eslint/typescript-eslint/issues/2323#issuecomment-663977655
Expand Down Expand Up @@ -1351,7 +1355,7 @@ let yyyy: Arr<Array<Array<Arr<string>>>> = [[[['2']]]];
messageId: 'errorStringGenericSimple',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 3,
column: 15,
column: 19,
},
],
},
Expand All @@ -1378,7 +1382,7 @@ interface ArrayClass<T> {
messageId: 'errorStringArraySimple',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 3,
column: 8,
column: 12,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Tests] Could you revert these changed columns? These changes don't seem to be related to this PR.

],
},
Expand All @@ -1399,7 +1403,7 @@ function barFunction(bar: Array<ArrayClass<String>>) {
messageId: 'errorStringGenericSimple',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 2,
column: 27,
column: 31,
},
],
},
Expand Down Expand Up @@ -1539,7 +1543,7 @@ let yyyy: Arr<Arr<string>[][]> = [[[['2']]]];
messageId: 'errorStringArray',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 3,
column: 15,
column: 19,
},
],
},
Expand All @@ -1564,7 +1568,7 @@ interface ArrayClass<T> {
messageId: 'errorStringArray',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 3,
column: 8,
column: 12,
},
],
},
Expand All @@ -1585,7 +1589,7 @@ function fooFunction(foo: ArrayClass<string>[]) {
messageId: 'errorStringArray',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 2,
column: 27,
column: 31,
},
],
},
Expand Down Expand Up @@ -1733,7 +1737,7 @@ let yyyy: Arr<Array<Array<Arr<string>>>> = [[[['2']]]];
messageId: 'errorStringGeneric',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 3,
column: 15,
column: 19,
},
],
},
Expand All @@ -1758,7 +1762,7 @@ interface ArrayClass<T> {
messageId: 'errorStringGeneric',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 4,
column: 8,
column: 12,
},
],
},
Expand All @@ -1779,7 +1783,7 @@ function barFunction(bar: Array<ArrayClass<String>>) {
messageId: 'errorStringGeneric',
data: { className: 'Array', readonlyPrefix: '', type: 'T' },
line: 2,
column: 27,
column: 31,
},
],
},
Expand Down Expand Up @@ -1839,7 +1843,7 @@ interface FooInterface {
messageId: 'errorStringGeneric',
data: { className: 'Array', readonlyPrefix: '', type: 'string' },
line: 3,
column: 18,
column: 22,
},
],
},
Expand Down Expand Up @@ -1918,6 +1922,21 @@ interface FooInterface {
},
],
},
{
code: "const x: Readonly<string[]> = ['a', 'b'];",
output: "const x: readonly string[] = ['a', 'b'];",
options: [{ default: 'array' }],
errors: [
{
messageId: 'errorStringArrayReadonly',
data: {
className: 'Readonly',
readonlyPrefix: 'readonly ',
type: 'string[]',
},
},
],
},
],
});

Expand Down