eslint-plugin-yenz 2.2.0-beta.2 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +4 -1
- package/.yarnrc.yml +1 -0
- package/README.md +40 -1
- package/index.js +1 -0
- package/lib/rules/export-at-end-of-file.js +149 -62
- package/package.json +3 -2
- package/test/fixtures.ts +8 -0
|
@@ -9,8 +9,11 @@ jobs:
|
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- uses: actions/checkout@v4
|
|
12
|
+
# Before setup-node cache: yarn — see https://github.com/actions/setup-node/issues/1027
|
|
13
|
+
- run: corepack enable
|
|
12
14
|
- uses: actions/setup-node@v6
|
|
13
15
|
with:
|
|
14
16
|
node-version: 24
|
|
15
|
-
|
|
17
|
+
cache: yarn
|
|
18
|
+
- run: yarn install --immutable
|
|
16
19
|
- run: yarn test
|
package/.yarnrc.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nodeLinker: node-modules
|
package/README.md
CHANGED
|
@@ -121,10 +121,46 @@ arr.filter((item) => item.active);
|
|
|
121
121
|
class Foo { bar = () => {} }
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### `yenz/export-at-end-of-file`
|
|
125
|
+
|
|
126
|
+
Disallows inline exports on function, class, type alias, and interface declarations. Prefer declaring first, then exporting all symbols in one statement at the end of the file. Auto-fixable.
|
|
127
|
+
|
|
128
|
+
> **Note:** This rule is *not* enabled in the `recommended`. Enable it explicitly or use `all`.
|
|
129
|
+
|
|
130
|
+
**Bad:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
export function parseDate(input: string) {
|
|
134
|
+
return new Date(input);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface UserProfile {
|
|
138
|
+
id: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type UserStatus = 'active' | 'inactive';
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Good:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
function parseDate(input: string) {
|
|
148
|
+
return new Date(input);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface UserProfile {
|
|
152
|
+
id: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type UserStatus = 'active' | 'inactive';
|
|
156
|
+
|
|
157
|
+
export { parseDate, type UserProfile, type UserStatus };
|
|
158
|
+
```
|
|
159
|
+
|
|
124
160
|
## Preset Configurations
|
|
125
161
|
|
|
126
162
|
- `**recommended**` - Enables `type-ordering` as error and `no-loops` as warning
|
|
127
|
-
- `**all**` - Enables
|
|
163
|
+
- `**all**` - Enables `type-ordering`, `no-loops`, and `no-named-arrow-functions` as errors
|
|
128
164
|
|
|
129
165
|
# Release Procedure
|
|
130
166
|
|
|
@@ -134,14 +170,17 @@ class Foo { bar = () => {} }
|
|
|
134
170
|
4. Add code samples in `test/` that intentionally fail your new or updated rules to confirm they are caught.
|
|
135
171
|
5. Commit and push your changes, then open a PR.
|
|
136
172
|
6. **Bump to a pre-release version and publish a beta:**
|
|
173
|
+
|
|
137
174
|
```bash
|
|
138
175
|
yarn version --pre[major|minor|patch] --preid beta
|
|
139
176
|
npm publish --tag beta # or alpha, rc
|
|
140
177
|
```
|
|
178
|
+
|
|
141
179
|
Users can test it with:
|
|
142
180
|
7. After review, **merge your branch into `main`**.
|
|
143
181
|
8. Open a version bump PR against `main` and merge it in.
|
|
144
182
|
9. **Publish the stable release** from `main`:
|
|
183
|
+
|
|
145
184
|
```bash
|
|
146
185
|
yarn version --[major|minor|patch]
|
|
147
186
|
npm publish
|
package/index.js
CHANGED
|
@@ -39,27 +39,49 @@ function isInlineExportableDeclaration(node) {
|
|
|
39
39
|
return EXPORTABLE_DECLARATION_TYPES.has(declaration.type) && Boolean(declaration.id);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Matches `export default function foo() {}`, `export default class Foo {}`, or
|
|
44
|
+
* TypeScript `export default interface …` / `export default type …` when the
|
|
45
|
+
* declaration is named. Anonymous `export default function () {}` is skipped
|
|
46
|
+
* because there is no local binding to list in a trailing specifier export.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} node - Top-level program statement node.
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function isInlineDefaultExportableDeclaration(node) {
|
|
52
|
+
if (node.type !== 'ExportDefaultDeclaration' || !node.declaration) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const { declaration } = node;
|
|
56
|
+
if (!EXPORTABLE_DECLARATION_TYPES.has(declaration.type)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return Boolean(declaration.id);
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
/**
|
|
43
63
|
* Extracts the exported name (and whether it's type-only) from an inline
|
|
44
64
|
* export declaration so it can be appended to the consolidated export list.
|
|
45
65
|
*
|
|
46
66
|
* @param {object} exportNamedNode - An `ExportNamedDeclaration` node that
|
|
47
67
|
* has already been validated by {@link isInlineExportableDeclaration}.
|
|
48
|
-
* @returns {{
|
|
49
|
-
*
|
|
50
|
-
* export statement.
|
|
68
|
+
* @returns {{ localName: string, exportedName: string, isType: boolean }}
|
|
69
|
+
* Export item describing the declared binding, its exported name, and
|
|
70
|
+
* whether it should be marked `type` in the final export statement.
|
|
51
71
|
*/
|
|
52
72
|
function getExportItem(exportNamedNode) {
|
|
53
73
|
const { declaration } = exportNamedNode;
|
|
74
|
+
const name = declaration.id.name;
|
|
54
75
|
return {
|
|
55
|
-
|
|
76
|
+
localName: name,
|
|
77
|
+
exportedName: name,
|
|
56
78
|
isType: TYPE_ONLY_DECLARATION_TYPES.has(declaration.type),
|
|
57
79
|
};
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
/**
|
|
61
|
-
* Reads
|
|
62
|
-
* can be merged with
|
|
83
|
+
* Reads named specifiers and an optional `… as default` from an existing
|
|
84
|
+
* specifier-only export so they can be merged with new items.
|
|
63
85
|
*
|
|
64
86
|
* Handles both forms of type-only exports:
|
|
65
87
|
* - Statement-level: `export type { Foo, Bar }` → `exportNode.exportKind === 'type'`
|
|
@@ -67,24 +89,35 @@ function getExportItem(exportNamedNode) {
|
|
|
67
89
|
*
|
|
68
90
|
* @param {object} exportNode - An `ExportNamedDeclaration` with no
|
|
69
91
|
* `declaration` and no `source` (i.e. a bare `export { … }`).
|
|
70
|
-
* @returns {
|
|
71
|
-
*
|
|
92
|
+
* @returns {{
|
|
93
|
+
* namedItems: Array<{ localName: string, exportedName: string, isType: boolean }>,
|
|
94
|
+
* defaultLocalName: string | null
|
|
95
|
+
* }}
|
|
72
96
|
*/
|
|
73
|
-
function
|
|
74
|
-
const
|
|
97
|
+
function parseSpecifierOnlyExport(exportNode) {
|
|
98
|
+
const namedItems = [];
|
|
99
|
+
let defaultLocalName = null;
|
|
75
100
|
for (const specifier of exportNode.specifiers) {
|
|
76
|
-
// Skip `export { x as default }` (handled by ExportSpecifier with non-Identifier
|
|
77
|
-
// exported, e.g. StringLiteral) and any non-ExportSpecifier nodes a parser
|
|
78
|
-
// might produce.
|
|
79
101
|
if (specifier.type !== 'ExportSpecifier' || specifier.exported.type !== 'Identifier') {
|
|
80
102
|
continue;
|
|
81
103
|
}
|
|
104
|
+
if (specifier.local.type !== 'Identifier') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (specifier.exported.name === 'default') {
|
|
108
|
+
defaultLocalName = specifier.local.name;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
82
111
|
const isType =
|
|
83
112
|
specifier.exportKind === 'type' ||
|
|
84
113
|
(exportNode.exportKind === 'type' && !exportNode.declaration);
|
|
85
|
-
|
|
114
|
+
namedItems.push({
|
|
115
|
+
localName: specifier.local.name,
|
|
116
|
+
exportedName: specifier.exported.name,
|
|
117
|
+
isType,
|
|
118
|
+
});
|
|
86
119
|
}
|
|
87
|
-
return
|
|
120
|
+
return { namedItems, defaultLocalName };
|
|
88
121
|
}
|
|
89
122
|
|
|
90
123
|
/**
|
|
@@ -107,61 +140,87 @@ function findSpecifierOnlyExport(programNode) {
|
|
|
107
140
|
* dropping duplicates by name (existing wins, so an existing `type` marker
|
|
108
141
|
* isn't accidentally downgraded to a value export).
|
|
109
142
|
*
|
|
110
|
-
* @param {Array<{
|
|
111
|
-
* @param {Array<{
|
|
112
|
-
* @returns {Array<{
|
|
113
|
-
* list of export items.
|
|
143
|
+
* @param {Array<{ localName: string, exportedName: string, isType: boolean }>} existingItems
|
|
144
|
+
* @param {Array<{ localName: string, exportedName: string, isType: boolean }>} addedItems
|
|
145
|
+
* @returns {Array<{ localName: string, exportedName: string, isType: boolean }>}
|
|
146
|
+
* Deduplicated, ordered list of export items (keyed by exported name).
|
|
114
147
|
*/
|
|
115
148
|
function mergeExportItems(existingItems, addedItems) {
|
|
116
149
|
const seen = new Set();
|
|
117
150
|
const merged = [];
|
|
118
151
|
for (const item of existingItems) {
|
|
119
|
-
if (seen.has(item.
|
|
120
|
-
seen.add(item.
|
|
152
|
+
if (seen.has(item.exportedName)) continue;
|
|
153
|
+
seen.add(item.exportedName);
|
|
121
154
|
merged.push(item);
|
|
122
155
|
}
|
|
123
156
|
for (const item of addedItems) {
|
|
124
|
-
if (seen.has(item.
|
|
125
|
-
seen.add(item.
|
|
157
|
+
if (seen.has(item.exportedName)) continue;
|
|
158
|
+
seen.add(item.exportedName);
|
|
126
159
|
merged.push(item);
|
|
127
160
|
}
|
|
128
161
|
return merged;
|
|
129
162
|
}
|
|
130
163
|
|
|
131
164
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* {
|
|
135
|
-
|
|
165
|
+
* @param {string | null} existingDefault
|
|
166
|
+
* @param {string | null} addedDefault
|
|
167
|
+
* @returns {string | null}
|
|
168
|
+
*/
|
|
169
|
+
function mergeDefaultExportLocal(existingDefault, addedDefault) {
|
|
170
|
+
if (addedDefault !== null) {
|
|
171
|
+
return addedDefault;
|
|
172
|
+
}
|
|
173
|
+
return existingDefault;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @param {{ localName: string, exportedName: string }} item */
|
|
177
|
+
function formatNamedSpecifierEntry(item) {
|
|
178
|
+
if (item.localName === item.exportedName) {
|
|
179
|
+
return item.localName;
|
|
180
|
+
}
|
|
181
|
+
return `${item.localName} as ${item.exportedName}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Renders named specifiers and an optional default as one export statement.
|
|
186
|
+
* When every named item is type-only and there is no default, uses
|
|
187
|
+
* `export type { … }`; otherwise uses `export { … }` with per-name `type`
|
|
188
|
+
* markers where needed.
|
|
136
189
|
*
|
|
137
|
-
* @param {Array<{
|
|
190
|
+
* @param {Array<{ localName: string, exportedName: string, isType: boolean }>} namedItems
|
|
191
|
+
* @param {string | null} defaultLocalName - Local binding listed as `name as default`.
|
|
138
192
|
* @returns {string} A single-line export statement (no trailing newline).
|
|
139
193
|
*/
|
|
140
|
-
function formatMergedSpecifierExport(
|
|
141
|
-
|
|
194
|
+
function formatMergedSpecifierExport(namedItems, defaultLocalName) {
|
|
195
|
+
const parts = namedItems.map((item) => {
|
|
196
|
+
const entry = formatNamedSpecifierEntry(item);
|
|
197
|
+
return item.isType ? `type ${entry}` : entry;
|
|
198
|
+
});
|
|
199
|
+
if (defaultLocalName !== null) {
|
|
200
|
+
parts.push(`${defaultLocalName} as default`);
|
|
201
|
+
}
|
|
202
|
+
if (parts.length === 0) {
|
|
142
203
|
return 'export {}';
|
|
143
204
|
}
|
|
144
|
-
|
|
145
|
-
|
|
205
|
+
const canUseExportTypeStatement =
|
|
206
|
+
defaultLocalName === null && namedItems.length > 0 && namedItems.every((item) => item.isType);
|
|
207
|
+
if (canUseExportTypeStatement) {
|
|
208
|
+
return `export type { ${namedItems.map((item) => formatNamedSpecifierEntry(item)).join(', ')} }`;
|
|
146
209
|
}
|
|
147
|
-
return `export { ${
|
|
148
|
-
.map((item) => (item.isType ? `type ${item.name}` : item.name))
|
|
149
|
-
.join(', ')} }`;
|
|
210
|
+
return `export { ${parts.join(', ')} }`;
|
|
150
211
|
}
|
|
151
212
|
|
|
152
213
|
/**
|
|
153
|
-
* Returns a location that covers only the
|
|
154
|
-
* `
|
|
155
|
-
* body, so editors underline the signature instead of the entire block.
|
|
214
|
+
* Returns a location that covers only the declaration header (through `(` or
|
|
215
|
+
* `{` before the body, or through `=` for type aliases), not the whole block.
|
|
156
216
|
*
|
|
157
|
-
* @param {object}
|
|
217
|
+
* @param {object} declaration - Function, class, interface, or type alias node.
|
|
218
|
+
* @param {object} reportStart - `loc.start` of the export statement.
|
|
219
|
+
* @param {object} endFallbackLoc - Full span if no tighter range applies.
|
|
158
220
|
* @param {import('eslint').SourceCode} sourceCode
|
|
159
221
|
* @returns {object} ESLint `SourceLocation` (`start` / `end` in line/column).
|
|
160
222
|
*/
|
|
161
|
-
function
|
|
162
|
-
const declaration = exportNamedNode.declaration;
|
|
163
|
-
const { start } = exportNamedNode.loc;
|
|
164
|
-
|
|
223
|
+
function getDeclarationSignatureLoc(declaration, reportStart, endFallbackLoc, sourceCode) {
|
|
165
224
|
const endsBeforeBraceBody =
|
|
166
225
|
(declaration.type === 'FunctionDeclaration'
|
|
167
226
|
|| declaration.type === 'ClassDeclaration'
|
|
@@ -171,7 +230,7 @@ function getInlineExportReportLoc(exportNamedNode, sourceCode) {
|
|
|
171
230
|
if (endsBeforeBraceBody) {
|
|
172
231
|
const bodyOpenIndex = declaration.body.range[0];
|
|
173
232
|
return {
|
|
174
|
-
start,
|
|
233
|
+
start: reportStart,
|
|
175
234
|
end: sourceCode.getLocFromIndex(bodyOpenIndex),
|
|
176
235
|
};
|
|
177
236
|
}
|
|
@@ -183,13 +242,25 @@ function getInlineExportReportLoc(exportNamedNode, sourceCode) {
|
|
|
183
242
|
});
|
|
184
243
|
if (equalsToken) {
|
|
185
244
|
return {
|
|
186
|
-
start,
|
|
245
|
+
start: reportStart,
|
|
187
246
|
end: sourceCode.getLocFromIndex(equalsToken.range[0]),
|
|
188
247
|
};
|
|
189
248
|
}
|
|
190
249
|
}
|
|
191
250
|
|
|
192
|
-
return
|
|
251
|
+
return endFallbackLoc;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function getInlineExportReportLoc(exportNamedNode, sourceCode) {
|
|
255
|
+
const declaration = exportNamedNode.declaration;
|
|
256
|
+
const { start } = exportNamedNode.loc;
|
|
257
|
+
return getDeclarationSignatureLoc(declaration, start, exportNamedNode.loc, sourceCode);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getInlineDefaultExportReportLoc(exportDefaultNode, sourceCode) {
|
|
261
|
+
const declaration = exportDefaultNode.declaration;
|
|
262
|
+
const { start } = exportDefaultNode.loc;
|
|
263
|
+
return getDeclarationSignatureLoc(declaration, start, exportDefaultNode.loc, sourceCode);
|
|
193
264
|
}
|
|
194
265
|
|
|
195
266
|
const exportAtEndOfFileRule = {
|
|
@@ -197,7 +268,7 @@ const exportAtEndOfFileRule = {
|
|
|
197
268
|
type: 'suggestion',
|
|
198
269
|
docs: {
|
|
199
270
|
description:
|
|
200
|
-
'Disallow inline export on function, class, type, or interface declarations; use
|
|
271
|
+
'Disallow inline export (including default export) on function, class, type, or interface declarations; use export specifiers at the end of the file',
|
|
201
272
|
recommended: false,
|
|
202
273
|
},
|
|
203
274
|
fixable: 'code',
|
|
@@ -213,47 +284,63 @@ const exportAtEndOfFileRule = {
|
|
|
213
284
|
// applies fixes in document order, and reporting the same set of edits
|
|
214
285
|
// from every violation would race and produce overlapping fixes.
|
|
215
286
|
'Program:exit'(programNode) {
|
|
216
|
-
const violations = programNode.body.filter(
|
|
287
|
+
const violations = programNode.body.filter(
|
|
288
|
+
(statement) =>
|
|
289
|
+
isInlineExportableDeclaration(statement)
|
|
290
|
+
|| isInlineDefaultExportableDeclaration(statement)
|
|
291
|
+
);
|
|
217
292
|
if (violations.length === 0) {
|
|
218
293
|
return;
|
|
219
294
|
}
|
|
220
295
|
|
|
221
|
-
const addedExportItems = violations
|
|
296
|
+
const addedExportItems = violations
|
|
297
|
+
.filter((node) => node.type === 'ExportNamedDeclaration')
|
|
298
|
+
.map(getExportItem);
|
|
299
|
+
|
|
300
|
+
let addedDefaultLocal = null;
|
|
301
|
+
for (const node of violations) {
|
|
302
|
+
if (node.type === 'ExportDefaultDeclaration' && node.declaration?.id) {
|
|
303
|
+
addedDefaultLocal = node.declaration.id.name;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
222
307
|
const lastViolation = violations[violations.length - 1];
|
|
223
308
|
|
|
224
309
|
violations.forEach((node) => {
|
|
310
|
+
const reportLoc =
|
|
311
|
+
node.type === 'ExportDefaultDeclaration'
|
|
312
|
+
? getInlineDefaultExportReportLoc(node, sourceCode)
|
|
313
|
+
: getInlineExportReportLoc(node, sourceCode);
|
|
314
|
+
|
|
225
315
|
context.report({
|
|
226
|
-
loc:
|
|
316
|
+
loc: reportLoc,
|
|
227
317
|
message:
|
|
228
318
|
'Declare this without inline export and list it in a single export statement at the end of the file.',
|
|
229
319
|
...(node === lastViolation
|
|
230
320
|
? {
|
|
231
321
|
fix(fixer) {
|
|
232
|
-
// Strip the leading `export` from each violation by
|
|
233
|
-
// replacing the whole `ExportNamedDeclaration` node with
|
|
234
|
-
// the source text of its inner declaration.
|
|
235
322
|
const edits = violations.map((exportNode) =>
|
|
236
323
|
fixer.replaceText(exportNode, sourceCode.getText(exportNode.declaration))
|
|
237
324
|
);
|
|
238
325
|
|
|
239
326
|
const existingExport = findSpecifierOnlyExport(programNode);
|
|
240
327
|
if (existingExport) {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
328
|
+
const { namedItems: existingNamed, defaultLocalName: existingDefault } =
|
|
329
|
+
parseSpecifierOnlyExport(existingExport);
|
|
330
|
+
const mergedNamed = mergeExportItems(existingNamed, addedExportItems);
|
|
331
|
+
const mergedDefault = mergeDefaultExportLocal(existingDefault, addedDefaultLocal);
|
|
245
332
|
edits.push(
|
|
246
|
-
fixer.replaceText(
|
|
333
|
+
fixer.replaceText(
|
|
334
|
+
existingExport,
|
|
335
|
+
formatMergedSpecifierExport(mergedNamed, mergedDefault)
|
|
336
|
+
)
|
|
247
337
|
);
|
|
248
338
|
} else {
|
|
249
|
-
// No bare export to merge into - append a fresh one
|
|
250
|
-
// after the program's last token. Ends with a newline to follow
|
|
251
|
-
// best practices.
|
|
252
339
|
const lastToken = sourceCode.getLastToken(programNode);
|
|
253
340
|
edits.push(
|
|
254
341
|
fixer.insertTextAfter(
|
|
255
342
|
lastToken,
|
|
256
|
-
`\n\n${formatMergedSpecifierExport(addedExportItems)}\n`
|
|
343
|
+
`\n\n${formatMergedSpecifierExport(addedExportItems, addedDefaultLocal)}\n`
|
|
257
344
|
)
|
|
258
345
|
);
|
|
259
346
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-yenz",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
|
+
"packageManager": "yarn@4.10.2",
|
|
4
5
|
"description": "Adds custom rules that Jens likes",
|
|
5
6
|
"repository": "https://github.com/JensAstrup/eslint-plugin-yenz",
|
|
6
7
|
"type": "module",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"license": "ISC",
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"eslint": "^10.0.1",
|
|
24
|
-
"typescript": "^
|
|
25
|
+
"typescript": "^6.0.3",
|
|
25
26
|
"typescript-eslint": "^8.0.0"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
package/test/fixtures.ts
CHANGED
|
@@ -47,3 +47,11 @@ export class ExportClass { method() { return 1; } } // expect-error yenz/export-
|
|
|
47
47
|
export type FixtureExportType = 1 // expect-error yenz/export-at-end-of-file // fix: type FixtureExportType = 1
|
|
48
48
|
|
|
49
49
|
export interface FixtureExportIface { n: number } // expect-error yenz/export-at-end-of-file // fix: interface FixtureExportIface { n: number }
|
|
50
|
+
|
|
51
|
+
export default function defaultExport() { return 1; } // expect-error yenz/export-at-end-of-file // fix: function defaultExport() { return 1; }
|
|
52
|
+
|
|
53
|
+
// Should pass (export-at-end-of-file — specifier exports and non-exported decls only):
|
|
54
|
+
function notExported() {}
|
|
55
|
+
function someExisting() {}
|
|
56
|
+
export { someExisting as someExistingPublic }
|
|
57
|
+
export { foo } from './other'
|