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

Transform type exports to "export type" in order to comply with isolatedModules #278

Merged
merged 7 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [14, 18]
node: [18, 20]
os: [ubuntu-latest, windows-latest]

name: Node ${{ matrix.node }} on ${{ matrix.os }}
Expand All @@ -24,11 +24,11 @@ jobs:
# frequently hangs for whatever reason,
# and we have no platform-specific code anyway…
- name: upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 18
if: matrix.os == 'ubuntu-latest' && matrix.node == 20
timeout-minutes: 1
continue-on-error: true
run: bash <(curl -s https://codecov.io/bash) -t ${{secrets.CODECOV_TOKEN}} -B ${{ github.ref }} -f coverage/coverage-final.json
# test the minimum supported peer dependency version
- run: npm install typescript@4.1 rollup@3.0
- run: npm install typescript@4.5 rollup@3.0
# aka `npm test` without the `pretest/build`
- run: node .build/tests/index.js
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 6.0.0

**Compatibility Notice**:

This release raises the minimum required TypeScript version to **4.5** and the minimum required node.js version to **18**.

**Fixes**:

- Export types with `export { type T }` syntax.

## 5.3.1

**Fixes**:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"homepage": "https://github.com/Swatinem/rollup-plugin-dts#readme",
"engines": {
"node": ">=v14.21.3"
"node": ">=v18.16.0"
},
"type": "module",
"main": "./dist/rollup-plugin-dts.cjs",
Expand Down Expand Up @@ -60,7 +60,7 @@
},
"peerDependencies": {
"rollup": "^3.0",
"typescript": "^4.1 || ^5.0"
"typescript": "^4.5 || ^5.0"
},
"optionalDependencies": {
"@babel/code-frame": "^7.22.5"
Expand Down
149 changes: 149 additions & 0 deletions src/transform/ExportsFixer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import ts from "typescript";

type NamedExport = {
localName: string;
exportedName: string;
kind: 'type' | 'value';
};
type ExportDeclaration = {
location: {
start: number;
end: number;
};
exports: Array<NamedExport>
}

export class ExportsFixer {
private readonly DEBUG = !!(process.env.DTS_EXPORTS_FIXER_DEBUG);
constructor(private readonly source: ts.SourceFile) {}

public fix(): string {
const exports = this.findExports();
exports.sort((a, b) => a.location.start - b.location.start);
return this.getCodeParts(exports).join('');
}

private findExports(): Array<ExportDeclaration> {
const { rawExports, values, types} = this.getExportsAndLocals();

return rawExports.map((rawExport) => {
const elements = rawExport.elements.map((e) => {
const exportedName = e.name.text;
const localName = e.propertyName?.text ?? e.name.text;
const kind = types.some(node => node.getText() === localName) && !values.some(node => node.getText() === localName) ? 'type' as const : 'value' as const;
return {
exportedName,
localName,
kind
}
})
return {
location: {
start: rawExport.getStart(),
end: rawExport.getEnd(),
},
exports: elements
};
});
}

private getExportsAndLocals(statements: Iterable<ts.Node> = this.source.statements) {
const rawExports: Array<ts.NamedExports> = [];
const values: Array<ts.Identifier> = [];
const types: Array<ts.Identifier> = [];

const recurseInto = (subStatements: Iterable<ts.Node>) => {
const { rawExports: subExports, values: subValues, types: subTypes} = this.getExportsAndLocals(subStatements);
rawExports.push(...subExports);
values.push(...subValues);
types.push(...subTypes);
};

for (const statement of statements) {
this.DEBUG && console.log(statement.getText(), statement.kind);
if (ts.isImportDeclaration(statement)) {
continue;
}
if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
this.DEBUG && console.log(`${statement.name.getFullText()} is a type`);
types.push(statement.name);
continue;
}
if (
ts.isEnumDeclaration(statement) ||
ts.isFunctionDeclaration(statement) ||
ts.isClassDeclaration(statement) ||
ts.isVariableStatement(statement)
) {
if (ts.isVariableStatement(statement)) {
for (const declaration of statement.declarationList.declarations) {
if (ts.isIdentifier(declaration.name)) {
this.DEBUG && console.log(`${declaration.name.getFullText()} is a value (from var statement)`);
values.push(declaration.name);
}
}
} else {
if (statement.name) {
this.DEBUG && console.log(`${statement.name.getFullText()} is a value (from declaration)`);
values.push(statement.name);
}
}
continue;
}
if (ts.isModuleBlock(statement)) {
const subStatements = statement.statements;
recurseInto(subStatements);
continue;
}
if (ts.isModuleDeclaration(statement)) {
recurseInto(statement.getChildren());
continue;
}
if (ts.isExportDeclaration(statement)) {
if (statement.moduleSpecifier) {
continue;
}
if (statement.isTypeOnly) {
// no fixup neccessary
continue;
}
const exportClause = statement.exportClause;
if (!exportClause || !ts.isNamedExports(exportClause)) {
continue;
}
rawExports.push(exportClause);
continue;
}
this.DEBUG && console.log('unhandled statement', statement.getFullText(), statement.kind);
}
return { rawExports, values, types };
}

private createNamedExport(exportSpec: NamedExport, elideType = false) {
return `${!elideType && exportSpec.kind === 'type' ? 'type ' : ''}${exportSpec.localName}${exportSpec.localName === exportSpec.exportedName ? '' : ` as ${exportSpec.exportedName}`}`;
}

private getCodeParts(exports: Array<ExportDeclaration>) {
let cursor = 0;
const code = this.source.getFullText();
const parts: Array<string> = [];
for (const exportDeclaration of exports) {
const head = code.slice(cursor, exportDeclaration.location.start);
if (head.length > 0) {
parts.push(head);
}
parts.push(this.getExportStatement(exportDeclaration));

cursor = exportDeclaration.location.end;
}
if (cursor < code.length) {
parts.push(code.slice(cursor));
}
return parts;
}

private getExportStatement(exportDeclaration: ExportDeclaration) {
const isTypeOnly = exportDeclaration.exports.every((e) => e.kind === 'type') && exportDeclaration.exports.length > 0;
return `${isTypeOnly ? 'type ' : ''}{ ${exportDeclaration.exports.map((exp) => this.createNamedExport(exp, isTypeOnly)).join(', ')} }`
}
}
11 changes: 7 additions & 4 deletions src/transform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ts from "typescript";
import { NamespaceFixer } from "./NamespaceFixer.js";
import { preProcess } from "./preprocess.js";
import { convert } from "./Transformer.js";
import {ExportsFixer} from "./ExportsFixer.js";

function parse(fileName: string, code: string): ts.SourceFile {
return ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true);
Expand Down Expand Up @@ -91,8 +92,8 @@ export const transform = () => {
return { code, ast: converted.ast as any, map: preprocessed.code.generateMap() as any };
},

renderChunk(code, chunk, options) {
const source = parse(chunk.fileName, code);
renderChunk(inputCode, chunk, options) {
const source = parse(chunk.fileName, inputCode);
const fixer = new NamespaceFixer(source);

const typeReferences = new Set<string>();
Expand Down Expand Up @@ -120,15 +121,17 @@ export const transform = () => {
}
}

code = writeBlock(Array.from(fileReferences, (ref) => `/// <reference path="${ref}" />`));
let code = writeBlock(Array.from(fileReferences, (ref) => `/// <reference path="${ref}" />`));
code += writeBlock(Array.from(typeReferences, (ref) => `/// <reference types="${ref}" />`));
code += fixer.fix();

if (!code) {
code += "\nexport { }";
}

return { code, map: { mappings: "" } };
const exportsFixer = new ExportsFixer(parse(chunk.fileName, code));
Swatinem marked this conversation as resolved.
Show resolved Hide resolved

return { code: exportsFixer.fix(), map: { mappings: "" } };
},
} satisfies Plugin;
};
Expand Down
2 changes: 1 addition & 1 deletion tests/testcases/call-signature/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ declare const fn: {
(arg: string): string;
staticProp: string;
};
export { I, fn };
export { type I, fn };
2 changes: 1 addition & 1 deletion tests/testcases/computed-property/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ type Klass = {
[0]: C;
[Dprop]: D;
};
export { Klass };
export type { Klass };
mithodin marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion tests/testcases/construct-signature/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface Foo {
new (): any;
}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/custom-tsconfig/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/export-as-namespace/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/export-default-interface/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo as default };
export type { Foo as default };
2 changes: 1 addition & 1 deletion tests/testcases/export-empty-object/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { }
export { }
2 changes: 1 addition & 1 deletion tests/testcases/export-multiple-vars/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ declare const options: {
declare const params: {
normalize: (inVar: In) => Out;
};
export { In, Out, config, options, params };
export { type In, type Out, config, options, params };
2 changes: 1 addition & 1 deletion tests/testcases/export-simple-alias/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
interface Foo {}
declare type Bar = Foo;
export { Bar };
export type { Bar };
2 changes: 1 addition & 1 deletion tests/testcases/export-simple-interface/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo };
export type { Foo };
4 changes: 1 addition & 3 deletions tests/testcases/export-star-as/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
interface A {}
type foo_d_A = A;
declare namespace foo_d {
export {
foo_d_A as A,
};
export type { foo_d_A as A };
}
export { foo_d as foo };
2 changes: 1 addition & 1 deletion tests/testcases/export-star/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
interface B {}
declare class A {}
export { A, B };
export { A, type B };
2 changes: 1 addition & 1 deletion tests/testcases/externals-link/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Container element type usable for mouse/touch functions
*/
type DragContainerElement = HTMLElement | SVGSVGElement | SVGGElement;
export { DragContainerElement };
export type { DragContainerElement };
2 changes: 1 addition & 1 deletion tests/testcases/externals/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export { ReactFragment } from 'react';
* Container element type usable for mouse/touch functions
*/
type DragContainerElement = HTMLElement | SVGSVGElement | SVGGElement;
export { DragContainerElement };
export type { DragContainerElement };
2 changes: 1 addition & 1 deletion tests/testcases/generic-extends/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ type AnimatedProps<T> = T;
type AnimatedComponent<T extends ElementType> = ForwardRefExoticComponent<
AnimatedProps<ComponentPropsWithRef<T>>
>;
export { AnimatedComponent, AnimatedProps };
export type { AnimatedComponent, AnimatedProps };
2 changes: 1 addition & 1 deletion tests/testcases/generics/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ declare function fn<T = G>(g: T, h: Gen<H>): void;
declare type TyFn = <T = J>(j: T, k: Gen<K>) => L;
declare type TyCtor = new <T = M>(m: T, n: Gen<N>) => O;
interface I2 extends Gen<P> {}
export { Cl, I1, I2, Ty, TyCtor, TyFn, fn };
export { Cl, type I1, type I2, type Ty, type TyCtor, type TyFn, fn };
2 changes: 1 addition & 1 deletion tests/testcases/implements-expression/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ interface MyComponentProps extends ns.Props<G> {
bar: string;
}
declare class MyComponent extends ns.Component<MyComponentProps> {}
export { MyComponent, MyComponentProps };
export { MyComponent, type MyComponentProps };
2 changes: 1 addition & 1 deletion tests/testcases/import-default-interface/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
interface Bar {}
interface Foo extends Bar {}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/import-no-import-clause/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/import-referenced-interface/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ interface Bar {}
interface Foo {
bar: Bar;
}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/import-renamed/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ interface Bar {}
interface Foo {
bar: Bar;
}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/import-unused-interface/expected.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
interface Foo {}
export { Foo };
export type { Foo };
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ interface Foo {
ns1: foo;
ns2: typeof foo;
}
export { Foo };
export type { Foo };
2 changes: 1 addition & 1 deletion tests/testcases/inline-import-generic/expected.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ interface Bar<T> {
interface Foo {
bar: Bar<number>;
}
export { Foo };
export type { Foo };
Loading