1 | "use strict";
|
2 | var __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 | }));
|
9 | var __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 | });
|
14 | var __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 | };
|
21 | Object.defineProperty(exports, "__esModule", { value: true });
|
22 | const utils_1 = require("@typescript-eslint/utils");
|
23 | const typescript_1 = require("typescript");
|
24 | const util = __importStar(require("../util"));
|
25 | exports.default = util.createRule({
|
26 | name: 'consistent-type-exports',
|
27 | meta: {
|
28 | type: 'suggestion',
|
29 | docs: {
|
30 | description: 'Enforces consistent usage of type exports',
|
31 | recommended: false,
|
32 | requiresTypeChecking: true,
|
33 | },
|
34 | messages: {
|
35 | typeOverValue: 'All exports in the declaration are only used as types. Use `export type`.',
|
36 | singleExportIsType: 'Type export {{exportNames}} is not a value and should be exported using `export type`.',
|
37 | multipleExportsAreTypes: 'Type exports {{exportNames}} are not values and should be exported using `export type`.',
|
38 | },
|
39 | schema: [
|
40 | {
|
41 | type: 'object',
|
42 | properties: {
|
43 | fixMixedExportsWithInlineTypeSpecifier: {
|
44 | type: 'boolean',
|
45 | },
|
46 | },
|
47 | additionalProperties: false,
|
48 | },
|
49 | ],
|
50 | fixable: 'code',
|
51 | },
|
52 | defaultOptions: [
|
53 | {
|
54 | fixMixedExportsWithInlineTypeSpecifier: false,
|
55 | },
|
56 | ],
|
57 | create(context, [{ fixMixedExportsWithInlineTypeSpecifier }]) {
|
58 | const sourceCode = context.getSourceCode();
|
59 | const sourceExportsMap = {};
|
60 | const parserServices = util.getParserServices(context);
|
61 | return {
|
62 | ExportNamedDeclaration(node) {
|
63 | var _a;
|
64 |
|
65 | const source = (_a = getSourceFromExport(node)) !== null && _a !== void 0 ? _a : 'undefined';
|
66 | const sourceExports = (sourceExportsMap[source] || (sourceExportsMap[source] = {
|
67 | source,
|
68 | reportValueExports: [],
|
69 | typeOnlyNamedExport: null,
|
70 | valueOnlyNamedExport: null,
|
71 | }));
|
72 |
|
73 |
|
74 | if (node.exportKind === 'type') {
|
75 | if (sourceExports.typeOnlyNamedExport == null) {
|
76 |
|
77 | sourceExports.typeOnlyNamedExport = node;
|
78 | }
|
79 | }
|
80 | else if (sourceExports.valueOnlyNamedExport == null) {
|
81 |
|
82 | sourceExports.valueOnlyNamedExport = node;
|
83 | }
|
84 |
|
85 | const typeBasedSpecifiers = [];
|
86 | const inlineTypeSpecifiers = [];
|
87 | const valueSpecifiers = [];
|
88 |
|
89 |
|
90 | if (node.exportKind !== 'type') {
|
91 | for (const specifier of node.specifiers) {
|
92 | if (specifier.exportKind === 'type') {
|
93 | inlineTypeSpecifiers.push(specifier);
|
94 | continue;
|
95 | }
|
96 | const isTypeBased = isSpecifierTypeBased(parserServices, specifier);
|
97 | if (isTypeBased === true) {
|
98 | typeBasedSpecifiers.push(specifier);
|
99 | }
|
100 | else if (isTypeBased === false) {
|
101 |
|
102 | valueSpecifiers.push(specifier);
|
103 | }
|
104 | }
|
105 | }
|
106 | if ((node.exportKind === 'value' && typeBasedSpecifiers.length) ||
|
107 | (node.exportKind === 'type' && valueSpecifiers.length)) {
|
108 | sourceExports.reportValueExports.push({
|
109 | node,
|
110 | typeBasedSpecifiers,
|
111 | valueSpecifiers,
|
112 | inlineTypeSpecifiers,
|
113 | });
|
114 | }
|
115 | },
|
116 | 'Program:exit'() {
|
117 | for (const sourceExports of Object.values(sourceExportsMap)) {
|
118 |
|
119 | if (sourceExports.reportValueExports.length === 0) {
|
120 | continue;
|
121 | }
|
122 | for (const report of sourceExports.reportValueExports) {
|
123 | if (report.valueSpecifiers.length === 0) {
|
124 |
|
125 | context.report({
|
126 | node: report.node,
|
127 | messageId: 'typeOverValue',
|
128 | *fix(fixer) {
|
129 | yield* fixExportInsertType(fixer, sourceCode, report.node);
|
130 | },
|
131 | });
|
132 | continue;
|
133 | }
|
134 |
|
135 | const allExportNames = report.typeBasedSpecifiers.map(specifier => `${specifier.local.name}`);
|
136 | if (allExportNames.length === 1) {
|
137 | const exportNames = allExportNames[0];
|
138 | context.report({
|
139 | node: report.node,
|
140 | messageId: 'singleExportIsType',
|
141 | data: { exportNames },
|
142 | *fix(fixer) {
|
143 | if (fixMixedExportsWithInlineTypeSpecifier) {
|
144 | yield* fixAddTypeSpecifierToNamedExports(fixer, report);
|
145 | }
|
146 | else {
|
147 | yield* fixSeparateNamedExports(fixer, sourceCode, report);
|
148 | }
|
149 | },
|
150 | });
|
151 | }
|
152 | else {
|
153 | const exportNames = util.formatWordList(allExportNames);
|
154 | context.report({
|
155 | node: report.node,
|
156 | messageId: 'multipleExportsAreTypes',
|
157 | data: { exportNames },
|
158 | *fix(fixer) {
|
159 | if (fixMixedExportsWithInlineTypeSpecifier) {
|
160 | yield* fixAddTypeSpecifierToNamedExports(fixer, report);
|
161 | }
|
162 | else {
|
163 | yield* fixSeparateNamedExports(fixer, sourceCode, report);
|
164 | }
|
165 | },
|
166 | });
|
167 | }
|
168 | }
|
169 | }
|
170 | },
|
171 | };
|
172 | },
|
173 | });
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | function isSpecifierTypeBased(parserServices, specifier) {
|
182 | const checker = parserServices.program.getTypeChecker();
|
183 | const node = parserServices.esTreeNodeToTSNodeMap.get(specifier.exported);
|
184 | const symbol = checker.getSymbolAtLocation(node);
|
185 | const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
186 | if (!aliasedSymbol || aliasedSymbol.escapedName === 'unknown') {
|
187 | return undefined;
|
188 | }
|
189 | return !(aliasedSymbol.flags & typescript_1.SymbolFlags.Value);
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | function* fixExportInsertType(fixer, sourceCode, node) {
|
200 | const exportToken = util.nullThrows(sourceCode.getFirstToken(node), util.NullThrowsReasons.MissingToken('export', node.type));
|
201 | yield fixer.insertTextAfter(exportToken, ' type');
|
202 | for (const specifier of node.specifiers) {
|
203 | if (specifier.exportKind === 'type') {
|
204 | const kindToken = util.nullThrows(sourceCode.getFirstToken(specifier), util.NullThrowsReasons.MissingToken('export', specifier.type));
|
205 | const firstTokenAfter = util.nullThrows(sourceCode.getTokenAfter(kindToken, {
|
206 | includeComments: true,
|
207 | }), 'Missing token following the export kind.');
|
208 | yield fixer.removeRange([kindToken.range[0], firstTokenAfter.range[0]]);
|
209 | }
|
210 | }
|
211 | }
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | function* fixSeparateNamedExports(fixer, sourceCode, report) {
|
218 | const { node, typeBasedSpecifiers, inlineTypeSpecifiers, valueSpecifiers } = report;
|
219 | const typeSpecifiers = typeBasedSpecifiers.concat(inlineTypeSpecifiers);
|
220 | const source = getSourceFromExport(node);
|
221 | const specifierNames = typeSpecifiers.map(getSpecifierText).join(', ');
|
222 | const exportToken = util.nullThrows(sourceCode.getFirstToken(node), util.NullThrowsReasons.MissingToken('export', node.type));
|
223 |
|
224 | const filteredSpecifierNames = valueSpecifiers
|
225 | .map(getSpecifierText)
|
226 | .join(', ');
|
227 | const openToken = util.nullThrows(sourceCode.getFirstToken(node, util.isOpeningBraceToken), util.NullThrowsReasons.MissingToken('{', node.type));
|
228 | const closeToken = util.nullThrows(sourceCode.getLastToken(node, util.isClosingBraceToken), util.NullThrowsReasons.MissingToken('}', node.type));
|
229 |
|
230 | yield fixer.replaceTextRange([openToken.range[1], closeToken.range[0]], ` ${filteredSpecifierNames} `);
|
231 |
|
232 | yield fixer.insertTextBefore(exportToken, `export type { ${specifierNames} }${source ? ` from '${source}'` : ''};\n`);
|
233 | }
|
234 | function* fixAddTypeSpecifierToNamedExports(fixer, report) {
|
235 | if (report.node.exportKind === 'type') {
|
236 | return;
|
237 | }
|
238 | for (const specifier of report.typeBasedSpecifiers) {
|
239 | yield fixer.insertTextBefore(specifier, 'type ');
|
240 | }
|
241 | }
|
242 |
|
243 |
|
244 |
|
245 | function getSourceFromExport(node) {
|
246 | var _a;
|
247 | if (((_a = node.source) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.Literal &&
|
248 | typeof node.source.value === 'string') {
|
249 | return node.source.value;
|
250 | }
|
251 | return undefined;
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | function getSpecifierText(specifier) {
|
258 | return `${specifier.local.name}${specifier.exported.name !== specifier.local.name
|
259 | ? ` as ${specifier.exported.name}`
|
260 | : ''}`;
|
261 | }
|
262 |
|
\ | No newline at end of file |