Skip to content

Commit cc451e6

Browse files
halfnelsonkaisermann
authored andcommitted
Use the typescript transform to remove type imports
should fix sveltejs#153 use first param of emit to only emit for the actual source file
1 parent a616dac commit cc451e6

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

src/transformers/typescript.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ function isValidSvelteImportDiagnostic(filename: string, diagnostic: any) {
7474
const importTransformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
7575
const visit: ts.Visitor = (node) => {
7676
if (ts.isImportDeclaration(node)) {
77+
if (node.importClause?.isTypeOnly) {
78+
return ts.createEmptyStatement();
79+
}
7780
return ts.createImportDeclaration(
7881
node.decorators,
7982
node.modifiers,
@@ -112,6 +115,72 @@ function isValidSvelteReactiveValueDiagnostic(
112115
return !(usedVar && proposedVar && usedVar === proposedVar);
113116
}
114117

118+
function createImportTransformerFromProgram(program: ts.Program) {
119+
const checker = program.getTypeChecker();
120+
121+
const importedTypeRemoverTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
122+
const visit: ts.Visitor = node => {
123+
if (ts.isImportDeclaration(node)) {
124+
125+
let newImportClause: ts.ImportClause = node.importClause;
126+
127+
if (node.importClause) {
128+
// import type {...} from './blah'
129+
if (node.importClause?.isTypeOnly) {
130+
return ts.createEmptyStatement();
131+
}
132+
133+
// import Blah, { blah } from './blah'
134+
newImportClause = ts.getMutableClone(node.importClause);
135+
136+
// types can't be default exports, so we just worry about { blah } and { blah as name } exports
137+
if (newImportClause.namedBindings && ts.isNamedImports(newImportClause.namedBindings)) {
138+
let newBindings = ts.getMutableClone(newImportClause.namedBindings);
139+
let newElements = [];
140+
141+
for (let spec of newBindings.elements) {
142+
let ident = spec.name;
143+
let symbol = checker.getSymbolAtLocation(ident);
144+
let aliased = checker.getAliasedSymbol(symbol);
145+
if (aliased) {
146+
if ((aliased.flags & (ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface)) > 0) {
147+
continue; //We found an imported type, don't add to our new import clause
148+
}
149+
}
150+
newElements.push(spec)
151+
}
152+
153+
if (newElements.length > 0) {
154+
newBindings.elements = ts.createNodeArray(newElements, newBindings.elements.hasTrailingComma);
155+
newImportClause.namedBindings = newBindings;
156+
} else {
157+
newImportClause.namedBindings = undefined;
158+
}
159+
}
160+
161+
//we ended up removing all named bindings and we didn't have a name? nothing left to import.
162+
if (!newImportClause.namedBindings && !newImportClause.name) {
163+
return ts.createEmptyStatement();
164+
}
165+
}
166+
167+
return ts.createImportDeclaration(
168+
node.decorators,
169+
node.modifiers,
170+
newImportClause,
171+
node.moduleSpecifier,
172+
);
173+
}
174+
return ts.visitEachChild(node, child => visit(child), context);
175+
};
176+
177+
return node => ts.visitNode(node, visit);
178+
};
179+
180+
return importedTypeRemoverTransformer;
181+
}
182+
183+
115184
function compileFileFromMemory(
116185
compilerOptions: CompilerOptions,
117186
{ filename, content }: { filename: string; content: string },
@@ -172,12 +241,15 @@ function compileFileFromMemory(
172241
};
173242

174243
const program = ts.createProgram([dummyFileName], compilerOptions, host);
244+
245+
let transformers = { before: [createImportTransformerFromProgram(program)] }
246+
175247
const emitResult = program.emit(
248+
program.getSourceFile(dummyFileName),
176249
undefined,
177250
undefined,
178251
undefined,
179-
undefined,
180-
TS_TRANSFORMERS,
252+
transformers
181253
);
182254

183255
// collect diagnostics without svelte import errors

test/fixtures/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type AType = "test1" | "test2"
2+
export interface AInterface {
3+
test: string
4+
}
5+
export const AValue: string = "test"
6+
7+
export default "String"

test/transformers/typescript.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,49 @@ describe('transformer - typescript', () => {
175175

176176
expect(diagnostics.some((d) => d.code === 2552)).toBe(true);
177177
});
178+
179+
it('should remove imports containing types only', async () => {
180+
const { code } = await transpile(
181+
`
182+
import { AType, AInterface } from './fixtures/types'
183+
let name: AType = "test1";
184+
`,
185+
);
186+
187+
expect(code).not.toContain('/fixtures/types');
188+
});
189+
190+
it('should remove type only imports', async () => {
191+
const { code } = await transpile(
192+
`
193+
import type { AType, AInterface } from './fixtures/types'
194+
let name: AType = "test1";
195+
`,
196+
);
197+
198+
expect(code).not.toContain('/fixtures/types');
199+
});
200+
201+
it('should remove only the types from the imports', async () => {
202+
const { code } = await transpile(
203+
`
204+
import { AValue, AType, AInterface } from './fixtures/types'
205+
let name: AType = "test1";
206+
`,
207+
);
208+
209+
expect(code).toContain("import { AValue } from './fixtures/types'");
210+
});
211+
212+
it('should remove the named imports completely if they were all types', async () => {
213+
const { code } = await transpile(
214+
`
215+
import Default, { AType, AInterface } from './fixtures/types'
216+
let name: AType = "test1";
217+
`,
218+
);
219+
220+
expect(code).toContain("import Default from './fixtures/types'");
221+
});
178222
});
179223
});

0 commit comments

Comments
 (0)