Skip to content

Commit

Permalink
prefer-export-from: Fix TypeScript compatibility (#1728)
Browse files Browse the repository at this point in the history
Co-authored-by: fisker Cheung <lionkay@gmail.com>
  • Loading branch information
nrgnrg and fisker committed Feb 16, 2022
1 parent 9de8a44 commit f14aa95
Show file tree
Hide file tree
Showing 4 changed files with 604 additions and 8 deletions.
32 changes: 27 additions & 5 deletions rules/prefer-export-from.js
Expand Up @@ -26,6 +26,10 @@ const getSpecifierName = node => {
}
};

const isTypeExport = specifier => specifier.exportKind === 'type' || specifier.parent.exportKind === 'type';

const isTypeImport = specifier => specifier.importKind === 'type' || specifier.parent.importKind === 'type';

function * removeSpecifier(node, fixer, sourceCode) {
const {parent} = node;
const {specifiers} = parent;
Expand Down Expand Up @@ -108,7 +112,17 @@ function getFixFunction({
const importDeclaration = imported.declaration;
const sourceNode = importDeclaration.source;
const sourceValue = sourceNode.value;
const exportDeclaration = exportDeclarations.find(({source}) => source.value === sourceValue);
const shouldExportAsType = imported.isTypeImport || exported.isTypeExport;

let exportDeclaration;
if (shouldExportAsType) {
// If a type export declaration already exists, reuse it, else use a value export declaration with an inline type specifier.
exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind === 'type');
}

if (!exportDeclaration) {
exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind !== 'type');
}

/** @param {import('eslint').Rule.RuleFixer} fixer */
return function * (fixer) {
Expand All @@ -118,24 +132,29 @@ function getFixFunction({
`\nexport * as ${exported.text} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
);
} else {
const specifier = exported.name === imported.name
let specifierText = exported.name === imported.name
? exported.text
: `${imported.text} as ${exported.text}`;

// Add an inline type specifier if the value is a type and the export deceleration is a value deceleration
if (shouldExportAsType && (!exportDeclaration || exportDeclaration.exportKind !== 'type')) {
specifierText = `type ${specifierText}`;
}

if (exportDeclaration) {
const lastSpecifier = exportDeclaration.specifiers[exportDeclaration.specifiers.length - 1];

// `export {} from 'foo';`
if (lastSpecifier) {
yield fixer.insertTextAfter(lastSpecifier, `, ${specifier}`);
yield fixer.insertTextAfter(lastSpecifier, `, ${specifierText}`);
} else {
const openingBraceToken = sourceCode.getFirstToken(exportDeclaration, isOpeningBraceToken);
yield fixer.insertTextAfter(openingBraceToken, specifier);
yield fixer.insertTextAfter(openingBraceToken, specifierText);
}
} else {
yield fixer.insertTextAfter(
program,
`\nexport {${specifier}} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
`\nexport {${specifierText}} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
);
}
}
Expand All @@ -156,13 +175,15 @@ function getExported(identifier, context, sourceCode) {
node: parent,
name: DEFAULT_SPECIFIER_NAME,
text: 'default',
isTypeExport: isTypeExport(parent),
};

case 'ExportSpecifier':
return {
node: parent,
name: getSpecifierName(parent.exported),
text: sourceCode.getText(parent.exported),
isTypeExport: isTypeExport(parent),
};

case 'VariableDeclarator': {
Expand Down Expand Up @@ -212,6 +233,7 @@ function getImported(variable, sourceCode) {
node: specifier,
declaration: specifier.parent,
variable,
isTypeImport: isTypeImport(specifier),
};

switch (specifier.type) {
Expand Down
114 changes: 111 additions & 3 deletions test/prefer-export-from.mjs
@@ -1,5 +1,5 @@
import outdent from 'outdent';
import {getTester} from './utils/test.mjs';
import {getTester, parsers} from './utils/test.mjs';

const {test} = getTester(import.meta);

Expand Down Expand Up @@ -318,7 +318,10 @@ test.snapshot({
],
});

test.typescript({
test.snapshot({
testerOptions: {
parser: parsers.typescript,
},
valid: [
// #1579
outdent`
Expand Down Expand Up @@ -347,8 +350,113 @@ test.typescript({
type AceEditor = import("ace-builds").Ace.Editor;
export {AceEditor};
`,
'export type { bar, foo } from "foo";',
],
invalid: [
outdent`
import { foo } from "foo";
export { foo };
export type { bar } from "foo";
`,
outdent`
import { foo } from "foo";
export { foo };
export { type bar } from "foo";
`,
outdent`
import { foo } from 'foo';
export { foo };
export type { bar } from "foo";
export { baz } from "foo";
`,
outdent`
import { foo } from 'foo';
export { foo };
export { type bar } from "foo";
export { baz } from "foo";
`,
outdent`
import type { foo } from "foo";
export type { foo };
export type { bar } from "foo";
`,
outdent`
import { foo } from 'foo';
export { foo };
export { baz } from "foo";
export { type bar } from "foo";
`,
outdent`
import type { foo } from "foo";
export type { foo };
export { type bar } from "foo";
`,
outdent`
import type { foo } from 'foo';
export type { foo };
export type { bar } from "foo";
export { baz } from "foo";
`,
outdent`
import type { foo } from 'foo';
export type { foo };
export { baz } from "foo";
export type { bar } from "foo";
`,
outdent`
import type { foo } from 'foo';
export type { foo };
export { type bar } from "foo";
export { baz } from "foo";
`,
outdent`
import type { foo } from 'foo';
export type { foo };
export { baz } from "foo";
export { type bar } from "foo";
`,
outdent`
import { type foo } from 'foo';
export type { foo };
`,
outdent`
import { foo } from 'foo';
export type { foo };
`,
outdent`
import type { foo } from 'foo';
export { type foo };
`,
outdent`
import type foo from "foo";
export default foo
`,
outdent`
import {type foo} from 'foo';
export {type foo as bar};
`,
outdent`
import {type foo} from 'foo';
export {type foo as bar};
export {type bar} from 'foo';
`,
outdent`
import {type foo as bar} from 'foo';
export {type bar as baz};
`,
outdent`
import {type foo as foo} from 'foo';
export {type foo as bar};
`,
outdent`
import {type foo as bar} from 'foo';
export {type bar as bar};
`,
outdent`
import {type foo as bar} from 'foo';
export {type bar as foo};
`,
],
invalid: [],
});

// `ignoreUsedVariables`
Expand Down

0 comments on commit f14aa95

Please sign in to comment.