UNPKG

9.99 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const util = __importStar(require("../util"));
24/**
25 * Check whatever node can be considered as simple
26 * @param node the node to be evaluated.
27 */
28function isSimpleType(node) {
29 switch (node.type) {
30 case utils_1.AST_NODE_TYPES.Identifier:
31 case utils_1.AST_NODE_TYPES.TSAnyKeyword:
32 case utils_1.AST_NODE_TYPES.TSBooleanKeyword:
33 case utils_1.AST_NODE_TYPES.TSNeverKeyword:
34 case utils_1.AST_NODE_TYPES.TSNumberKeyword:
35 case utils_1.AST_NODE_TYPES.TSBigIntKeyword:
36 case utils_1.AST_NODE_TYPES.TSObjectKeyword:
37 case utils_1.AST_NODE_TYPES.TSStringKeyword:
38 case utils_1.AST_NODE_TYPES.TSSymbolKeyword:
39 case utils_1.AST_NODE_TYPES.TSUnknownKeyword:
40 case utils_1.AST_NODE_TYPES.TSVoidKeyword:
41 case utils_1.AST_NODE_TYPES.TSNullKeyword:
42 case utils_1.AST_NODE_TYPES.TSArrayType:
43 case utils_1.AST_NODE_TYPES.TSUndefinedKeyword:
44 case utils_1.AST_NODE_TYPES.TSThisType:
45 case utils_1.AST_NODE_TYPES.TSQualifiedName:
46 return true;
47 case utils_1.AST_NODE_TYPES.TSTypeReference:
48 if (node.typeName &&
49 node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
50 node.typeName.name === 'Array') {
51 if (!node.typeParameters) {
52 return true;
53 }
54 if (node.typeParameters.params.length === 1) {
55 return isSimpleType(node.typeParameters.params[0]);
56 }
57 }
58 else {
59 if (node.typeParameters) {
60 return false;
61 }
62 return isSimpleType(node.typeName);
63 }
64 return false;
65 default:
66 return false;
67 }
68}
69/**
70 * Check if node needs parentheses
71 * @param node the node to be evaluated.
72 */
73function typeNeedsParentheses(node) {
74 switch (node.type) {
75 case utils_1.AST_NODE_TYPES.TSTypeReference:
76 return typeNeedsParentheses(node.typeName);
77 case utils_1.AST_NODE_TYPES.TSUnionType:
78 case utils_1.AST_NODE_TYPES.TSFunctionType:
79 case utils_1.AST_NODE_TYPES.TSIntersectionType:
80 case utils_1.AST_NODE_TYPES.TSTypeOperator:
81 case utils_1.AST_NODE_TYPES.TSInferType:
82 return true;
83 case utils_1.AST_NODE_TYPES.Identifier:
84 return node.name === 'ReadonlyArray';
85 default:
86 return false;
87 }
88}
89const arrayOption = { enum: ['array', 'generic', 'array-simple'] };
90exports.default = util.createRule({
91 name: 'array-type',
92 meta: {
93 type: 'suggestion',
94 docs: {
95 description: 'Requires using either `T[]` or `Array<T>` for arrays',
96 // too opinionated to be recommended
97 recommended: false,
98 },
99 fixable: 'code',
100 messages: {
101 errorStringGeneric: "Array type using '{{readonlyPrefix}}{{type}}[]' is forbidden. Use '{{className}}<{{type}}>' instead.",
102 errorStringArray: "Array type using '{{className}}<{{type}}>' is forbidden. Use '{{readonlyPrefix}}{{type}}[]' instead.",
103 errorStringArraySimple: "Array type using '{{className}}<{{type}}>' is forbidden for simple types. Use '{{readonlyPrefix}}{{type}}[]' instead.",
104 errorStringGenericSimple: "Array type using '{{readonlyPrefix}}{{type}}[]' is forbidden for non-simple types. Use '{{className}}<{{type}}>' instead.",
105 },
106 schema: [
107 {
108 type: 'object',
109 properties: {
110 default: arrayOption,
111 readonly: arrayOption,
112 },
113 },
114 ],
115 },
116 defaultOptions: [
117 {
118 default: 'array',
119 },
120 ],
121 create(context, [options]) {
122 var _a;
123 const sourceCode = context.getSourceCode();
124 const defaultOption = options.default;
125 const readonlyOption = (_a = options.readonly) !== null && _a !== void 0 ? _a : defaultOption;
126 /**
127 * @param node the node to be evaluated.
128 */
129 function getMessageType(node) {
130 if (node && isSimpleType(node)) {
131 return sourceCode.getText(node);
132 }
133 return 'T';
134 }
135 return {
136 TSArrayType(node) {
137 const isReadonly = node.parent &&
138 node.parent.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
139 node.parent.operator === 'readonly';
140 const currentOption = isReadonly ? readonlyOption : defaultOption;
141 if (currentOption === 'array' ||
142 (currentOption === 'array-simple' && isSimpleType(node.elementType))) {
143 return;
144 }
145 const messageId = currentOption === 'generic'
146 ? 'errorStringGeneric'
147 : 'errorStringGenericSimple';
148 const errorNode = isReadonly ? node.parent : node;
149 context.report({
150 node: errorNode,
151 messageId,
152 data: {
153 className: isReadonly ? 'ReadonlyArray' : 'Array',
154 readonlyPrefix: isReadonly ? 'readonly ' : '',
155 type: getMessageType(node.elementType),
156 },
157 fix(fixer) {
158 const typeNode = node.elementType;
159 const arrayType = isReadonly ? 'ReadonlyArray' : 'Array';
160 return [
161 fixer.replaceTextRange([errorNode.range[0], typeNode.range[0]], `${arrayType}<`),
162 fixer.replaceTextRange([typeNode.range[1], errorNode.range[1]], '>'),
163 ];
164 },
165 });
166 },
167 TSTypeReference(node) {
168 var _a, _b;
169 if (node.typeName.type !== utils_1.AST_NODE_TYPES.Identifier ||
170 !(node.typeName.name === 'Array' ||
171 node.typeName.name === 'ReadonlyArray')) {
172 return;
173 }
174 const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray';
175 const currentOption = isReadonlyArrayType
176 ? readonlyOption
177 : defaultOption;
178 if (currentOption === 'generic') {
179 return;
180 }
181 const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : '';
182 const typeParams = (_a = node.typeParameters) === null || _a === void 0 ? void 0 : _a.params;
183 const messageId = currentOption === 'array'
184 ? 'errorStringArray'
185 : 'errorStringArraySimple';
186 if (!typeParams || typeParams.length === 0) {
187 // Create an 'any' array
188 context.report({
189 node,
190 messageId,
191 data: {
192 className: isReadonlyArrayType ? 'ReadonlyArray' : 'Array',
193 readonlyPrefix,
194 type: 'any',
195 },
196 fix(fixer) {
197 return fixer.replaceText(node, `${readonlyPrefix}any[]`);
198 },
199 });
200 return;
201 }
202 if (typeParams.length !== 1 ||
203 (currentOption === 'array-simple' && !isSimpleType(typeParams[0]))) {
204 return;
205 }
206 const type = typeParams[0];
207 const typeParens = !util.isParenthesized(type, sourceCode) && typeNeedsParentheses(type);
208 const parentParens = readonlyPrefix &&
209 ((_b = node.parent) === null || _b === void 0 ? void 0 : _b.type) === utils_1.AST_NODE_TYPES.TSArrayType &&
210 !util.isParenthesized(node.parent.elementType, sourceCode);
211 const start = `${parentParens ? '(' : ''}${readonlyPrefix}${typeParens ? '(' : ''}`;
212 const end = `${typeParens ? ')' : ''}[]${parentParens ? ')' : ''}`;
213 context.report({
214 node,
215 messageId,
216 data: {
217 className: isReadonlyArrayType ? 'ReadonlyArray' : 'Array',
218 readonlyPrefix,
219 type: getMessageType(type),
220 },
221 fix(fixer) {
222 return [
223 fixer.replaceTextRange([node.range[0], type.range[0]], start),
224 fixer.replaceTextRange([type.range[1], node.range[1]], end),
225 ];
226 },
227 });
228 },
229 };
230 },
231});
232//# sourceMappingURL=array-type.js.map
\No newline at end of file