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

fix(eslint-plugin): [array-type] support readonly operator #429

Merged
merged 4 commits into from Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -69,7 +69,7 @@
"lerna": "^3.10.5",
"lint-staged": "8.1.0",
"lodash.isplainobject": "4.0.6",
"prettier": "^1.14.3",
"prettier": "^1.17.0",
"rimraf": "^2.6.3",
"ts-jest": "^24.0.0",
"ts-node": "^8.0.1",
Expand Down
37 changes: 31 additions & 6 deletions packages/eslint-plugin/src/rules/array-type.ts
Expand Up @@ -65,6 +65,8 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean {
case AST_NODE_TYPES.TSTypeOperator:
case AST_NODE_TYPES.TSInferType:
return true;
case AST_NODE_TYPES.Identifier:
return node.name === 'ReadonlyArray';
default:
return false;
}
Expand Down Expand Up @@ -153,8 +155,14 @@ export default util.createRule<Options, MessageIds>({
? 'errorStringGeneric'
: 'errorStringGenericSimple';

const isReadonly =
node.parent &&
node.parent.type === AST_NODE_TYPES.TSTypeOperator &&
node.parent.operator === 'readonly';
const typeOpNode = isReadonly ? node.parent! : null;

context.report({
node,
node: isReadonly ? node.parent! : node,
messageId,
data: {
type: getMessageType(node.elementType),
Expand All @@ -163,8 +171,20 @@ export default util.createRule<Options, MessageIds>({
const startText = requireWhitespaceBefore(node);
const toFix = [
fixer.replaceTextRange([node.range[1] - 2, node.range[1]], '>'),
fixer.insertTextBefore(node, `${startText ? ' ' : ''}Array<`),
fixer.insertTextBefore(
node,
`${startText ? ' ' : ''}${isReadonly ? 'Readonly' : ''}Array<`,
),
];
if (typeOpNode) {
// remove the readonly operator if it exists
toFix.unshift(
fixer.removeRange([
typeOpNode.range[0],
typeOpNode.range[0] + 'readonly '.length,
]),
);
}

if (node.elementType.type === AST_NODE_TYPES.TSParenthesizedType) {
const first = sourceCode.getFirstToken(node.elementType);
Expand All @@ -184,13 +204,18 @@ export default util.createRule<Options, MessageIds>({
TSTypeReference(node: TSESTree.TSTypeReference) {
if (
option === 'generic' ||
node.typeName.type !== AST_NODE_TYPES.Identifier ||
node.typeName.name !== 'Array'
node.typeName.type !== AST_NODE_TYPES.Identifier
) {
return;
}
if (!['Array', 'ReadonlyArray'].includes(node.typeName.name)) {
return;
}

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

const typeParams = node.typeParameters && node.typeParameters.params;

Expand All @@ -203,7 +228,7 @@ export default util.createRule<Options, MessageIds>({
type: 'any',
},
fix(fixer) {
return fixer.replaceText(node, 'any[]');
return fixer.replaceText(node, `${readonlyPrefix}any[]`);
},
});
return;
Expand All @@ -229,7 +254,7 @@ export default util.createRule<Options, MessageIds>({
return [
fixer.replaceTextRange(
[node.range[0], type.range[0]],
parens ? '(' : '',
`${readonlyPrefix}${parens ? '(' : ''}`,
),
fixer.replaceTextRange(
[type.range[1], node.range[1]],
Expand Down
12 changes: 6 additions & 6 deletions packages/eslint-plugin/src/rules/unified-signatures.ts
Expand Up @@ -136,7 +136,7 @@ export default util.createRule({
}

function checkOverloads(
signatures: ReadonlyArray<OverloadNode[]>,
signatures: readonly OverloadNode[][],
typeParameters?: TSESTree.TSTypeParameterDeclaration,
): Failure[] {
const result: Failure[] = [];
Expand Down Expand Up @@ -213,8 +213,8 @@ export default util.createRule({

/** Detect `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`. */
function signaturesDifferBySingleParameter(
types1: ReadonlyArray<TSESTree.Parameter>,
types2: ReadonlyArray<TSESTree.Parameter>,
types1: readonly TSESTree.Parameter[],
types2: readonly TSESTree.Parameter[],
): Unify | undefined {
const index = getIndexOfFirstDifference(
types1,
Expand Down Expand Up @@ -436,8 +436,8 @@ export default util.createRule({

/* Returns the first index where `a` and `b` differ. */
function getIndexOfFirstDifference<T>(
a: ReadonlyArray<T>,
b: ReadonlyArray<T>,
a: readonly T[],
b: readonly T[],
equal: util.Equal<T>,
): number | undefined {
for (let i = 0; i < a.length && i < b.length; i++) {
Expand All @@ -450,7 +450,7 @@ export default util.createRule({

/** Calls `action` for every pair of values in `values`. */
function forEachPair<T>(
values: ReadonlyArray<T>,
values: readonly T[],
action: (a: T, b: T) => void,
): void {
for (let i = 0; i < values.length; i++) {
Expand Down
115 changes: 99 additions & 16 deletions packages/eslint-plugin/tests/rules/array-type.test.ts
Expand Up @@ -9,7 +9,7 @@ const ruleTester = new RuleTester({
ruleTester.run('array-type', rule, {
valid: [
{
code: 'let a = []',
code: 'let a: readonly any[] = []',
options: ['array'],
},
{
Expand Down Expand Up @@ -826,31 +826,87 @@ let yyyy: Arr<Array<Array<Arr<string>>>> = [[[["2"]]]];`,
},
],
},

// readonly tests
{
code: 'const x: readonly number[] = [];',
output: 'const x: ReadonlyArray<number> = [];',
options: ['generic'],
errors: [
{
messageId: 'errorStringGeneric',
data: { type: 'number' },
line: 1,
column: 10,
},
],
},
{
code: 'const x: readonly (number | string | boolean)[] = [];',
output: 'const x: ReadonlyArray<number | string | boolean> = [];',
options: ['generic'],
errors: [
{
messageId: 'errorStringGeneric',
data: { type: 'T' },
line: 1,
column: 10,
},
],
},
{
code: 'const x: ReadonlyArray<number> = [];',
output: 'const x: readonly number[] = [];',
options: ['array'],
errors: [
{
messageId: 'errorStringArray',
data: { type: 'number' },
line: 1,
column: 10,
},
],
},
{
code: 'const x: ReadonlyArray<number | string | boolean> = [];',
output: 'const x: readonly (number | string | boolean)[] = [];',
options: ['array'],
errors: [
{
messageId: 'errorStringArray',
data: { type: 'T' },
line: 1,
column: 10,
},
],
},
],
});

// eslint rule tester is not working with multi-pass
// https://github.com/eslint/eslint/issues/11187
describe('array-type (nested)', () => {
it('should fix correctly', () => {
describe('should deeply fix correctly', () => {
function testOutput(option: string, code: string, output: string): void {
const linter = new Linter();
it(code, () => {
const linter = new Linter();

linter.defineRule('array-type', Object.assign({}, rule) as any);
const result = linter.verifyAndFix(
code,
{
rules: {
'array-type': [2, option],
linter.defineRule('array-type', Object.assign({}, rule) as any);
const result = linter.verifyAndFix(
code,
{
rules: {
'array-type': [2, option],
},
parser: '@typescript-eslint/parser',
},
parser: '@typescript-eslint/parser',
},
{
fix: true,
},
);
{
fix: true,
},
);

expect(output).toBe(result.output);
expect(result.output).toBe(output);
});
}

testOutput(
Expand Down Expand Up @@ -894,5 +950,32 @@ class Foo<T = Bar[][]> extends Bar<T, T[]> implements Baz<T[]> {
`let yy: number[][] = [[4, 5], [6]];`,
`let yy: Array<Array<number>> = [[4, 5], [6]];`,
);

// readonly
testOutput(
'generic',
`let x: readonly number[][]`,
`let x: ReadonlyArray<Array<number>>`,
);
testOutput(
'generic',
`let x: readonly (readonly number[])[]`,
`let x: ReadonlyArray<ReadonlyArray<number>>`,
);
testOutput(
'array',
`let x: ReadonlyArray<Array<number>>`,
`let x: readonly number[][]`,
);
testOutput(
'array',
`let x: ReadonlyArray<ReadonlyArray<number>>`,
`let x: readonly (readonly number[])[]`,
);
testOutput(
'array',
`let x: ReadonlyArray<readonly number[]>`,
`let x: readonly (readonly number[])[]`,
);
});
});
4 changes: 2 additions & 2 deletions packages/eslint-plugin/typings/eslint-utils.d.ts
Expand Up @@ -74,7 +74,7 @@ declare module 'eslint-utils' {
globalScope: Scope.Scope,
options?: {
mode: 'strict' | 'legacy';
globalObjectNames: ReadonlyArray<string>;
globalObjectNames: readonly string[];
},
);

Expand Down Expand Up @@ -103,7 +103,7 @@ declare module 'eslint-utils' {
}
export interface FoundReference<T = any> {
node: TSESTree.Node;
path: ReadonlyArray<string>;
path: readonly string[];
type: ReferenceType;
entry: T;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-estree/src/node-utils.ts
Expand Up @@ -678,7 +678,7 @@ export function nodeHasTokens(n: ts.Node, ast: ts.SourceFile) {
* @param callback
*/
export function firstDefined<T, U>(
array: ReadonlyArray<T> | undefined,
array: readonly T[] | undefined,
callback: (element: T, index: number) => U | undefined,
): U | undefined {
if (array === undefined) {
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-estree/src/semantic-errors.ts
Expand Up @@ -52,8 +52,8 @@ export function getFirstSemanticOrSyntacticError(
}

function whitelistSupportedDiagnostics(
diagnostics: ReadonlyArray<ts.DiagnosticWithLocation | ts.Diagnostic>,
): ReadonlyArray<ts.DiagnosticWithLocation | ts.Diagnostic> {
diagnostics: readonly (ts.DiagnosticWithLocation | ts.Diagnostic)[],
): readonly (ts.DiagnosticWithLocation | ts.Diagnostic)[] {
return diagnostics.filter(diagnostic => {
switch (diagnostic.code) {
case 1013: // ts 3.2 "A rest parameter or binding pattern may not have a trailing comma."
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-estree/src/ts-estree/ts-estree.ts
Expand Up @@ -1320,7 +1320,7 @@ export interface TSTypeLiteral extends BaseNode {

export interface TSTypeOperator extends BaseNode {
type: AST_NODE_TYPES.TSTypeOperator;
operator: 'keyof' | 'unique';
operator: 'keyof' | 'unique' | 'readonly';
typeAnnotation?: TSTypeAnnotation;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/typescript-estree/src/tsconfig-parser.ts
Expand Up @@ -152,9 +152,9 @@ export function calculateProjectParserOptions(
const oldReadDirectory = host.readDirectory;
host.readDirectory = (
path: string,
extensions?: ReadonlyArray<string>,
exclude?: ReadonlyArray<string>,
include?: ReadonlyArray<string>,
extensions?: readonly string[],
exclude?: readonly string[],
include?: readonly string[],
depth?: number,
) =>
oldReadDirectory(
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -5799,10 +5799,10 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=

prettier@^1.14.3:
version "1.16.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
prettier@^1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008"
integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==

pretty-format@^23.6.0:
version "23.6.0"
Expand Down