Skip to content

Commit

Permalink
correcly handle imports with aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
callmephilip committed Feb 3, 2025
1 parent 9ba360e commit ece640d
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 167 deletions.
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jurassic/jurassic",
"version": "0.1.49",
"version": "0.1.50",
"license": "MIT",
"tasks": {
"build": "deno run -A --reload jsr:@jurassic/jurassic/export . && deno task runnbs && deno check . && deno lint && deno fmt && deno task clean && deno test --allow-all --env-file",
Expand Down
103 changes: 56 additions & 47 deletions jurassic/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,14 @@ export const removeDuplicateImports = (sourceCode: string): string => {
ts.ScriptTarget.Latest,
true,
);
const imports = new Map<
string,
{
specifiers: Set<string>;
defaultName?: string;
namespaceImport?: string;
typeOnlySpecifiers: Set<string>;
isBareImport?: boolean;
}
>();
const importNodes: ts.ImportDeclaration[] = [];
const imports = new Map<string, {
specifiers: Map<string, string>; // name -> alias
defaultName?: string;
namespaceImport?: string;
typeOnlySpecifiers: Map<string, string>; // name -> alias
isBareImport?: boolean;
}>();

function visit(node: ts.Node) {
if (ts.isImportDeclaration(node)) {
Expand All @@ -285,47 +282,54 @@ export const removeDuplicateImports = (sourceCode: string): string => {

if (!imports.has(source)) {
imports.set(source, {
specifiers: new Set(),
typeOnlySpecifiers: new Set(),
specifiers: new Map(),
typeOnlySpecifiers: new Map(),
isBareImport: !node.importClause,
});
}

// Handle bare imports
if (!node.importClause) {
imports.get(source)!.isBareImport = true;
return;
}

if (node.importClause) {
const isTypeOnly = node.importClause.isTypeOnly;

if (
node.importClause.namedBindings &&
ts.isNamespaceImport(node.importClause.namedBindings)
) {
imports.get(source)!.namespaceImport =
node.importClause.namedBindings.name.text;
} else if (
node.importClause.namedBindings &&
ts.isNamedImports(node.importClause.namedBindings)
) {
node.importClause.namedBindings.elements.forEach((element) => {
if (isTypeOnly || element.isTypeOnly) {
imports.get(source)?.typeOnlySpecifiers.add(element.name.text);
const isTypeOnly = node.importClause.isTypeOnly;

if (
node.importClause.namedBindings &&
ts.isNamespaceImport(node.importClause.namedBindings)
) {
imports.get(source)!.namespaceImport =
node.importClause.namedBindings.name.text;
} else if (
node.importClause.namedBindings &&
ts.isNamedImports(node.importClause.namedBindings)
) {
node.importClause.namedBindings.elements.forEach((element) => {
const name = element.propertyName?.text || element.name.text;
const alias = element.propertyName ? element.name.text : undefined;

if (isTypeOnly || element.isTypeOnly) {
if (alias) {
imports.get(source)?.typeOnlySpecifiers.set(name, alias);
} else {
imports.get(source)?.specifiers.add(element.name.text);
imports.get(source)?.typeOnlySpecifiers.set(name, name);
}
});
}
if (node.importClause.name) {
imports.get(source)!.defaultName = node.importClause.name.text;
if (isTypeOnly) {
imports.get(source)?.typeOnlySpecifiers.add("default");
} else {
imports.get(source)?.specifiers.add("default");
if (alias) {
imports.get(source)?.specifiers.set(name, alias);
} else {
imports.get(source)?.specifiers.set(name, name);
}
}
}
});
}
if (node.importClause.name) {
imports.get(source)!.defaultName = node.importClause.name.text;
const target = isTypeOnly
? imports.get(source)?.typeOnlySpecifiers
: imports.get(source)?.specifiers;
target?.set("default", "default");
}
}
ts.forEachChild(node, visit);
Expand All @@ -346,29 +350,32 @@ export const removeDuplicateImports = (sourceCode: string): string => {
},
] of imports
) {
// Handle bare imports first
if (isBareImport) {
newImports.push(`import '${source}';`);
continue;
}

// Handle type-only imports
if (typeOnlySpecifiers.size > 0) {
let importStr = "import type ";
const namedImports = Array.from(typeOnlySpecifiers).sort().join(", ");
importStr += `{ ${namedImports} }`;
importStr += ` from '${source}';`;
let importStr = "import type { ";
const namedImports = Array.from(typeOnlySpecifiers.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, alias]) => name === alias ? name : `${name} as ${alias}`)
.join(", ");
importStr += namedImports;
importStr += ` } from '${source}';`;
newImports.push(importStr);
}

// Handle regular imports
if (specifiers.size > 0 || namespaceImport) {
let importStr = "import ";
if (namespaceImport) {
importStr += `* as ${namespaceImport}`;
} else {
const hasDefault = specifiers.delete("default");
const namedImports = Array.from(specifiers).sort().join(", ");
const namedImports = Array.from(specifiers.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, alias]) => name === alias ? name : `${name} as ${alias}`)
.join(", ");

if (hasDefault && defaultName) {
importStr += `${defaultName} `;
Expand Down Expand Up @@ -414,6 +421,7 @@ Deno.test("findDenoTests", () => {
Deno.test("removeDuplicateImports", () => {
assertEquals(
removeDuplicateImports(`
import { Record as Message } from "tinychat/api/types/chat/tinychat/core/message.ts";
import type { Config } from "jurassic/config.ts";
import * as ts from "typescript";
import { assertEquals } from "jsr:@std/assert";
Expand All @@ -427,7 +435,8 @@ import path from "node:path";
Deno.test("t2", () => {});
`),
`import type { Config } from 'jurassic/config.ts';
`import { Record as Message } from 'tinychat/api/types/chat/tinychat/core/message.ts';
import type { Config } from 'jurassic/config.ts';
import * as ts from 'typescript';
import { assertEquals } from 'jsr:@std/assert';
import path from 'node:path';
Expand Down
99 changes: 53 additions & 46 deletions jurassic/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,14 @@ export const removeDuplicateImports = (sourceCode: string): string => {
ts.ScriptTarget.Latest,
true,
);
const imports = new Map<
string,
{
specifiers: Set<string>;
defaultName?: string;
namespaceImport?: string;
typeOnlySpecifiers: Set<string>;
isBareImport?: boolean;
}
>();
const importNodes: ts.ImportDeclaration[] = [];
const imports = new Map<string, {
specifiers: Map<string, string>; // name -> alias
defaultName?: string;
namespaceImport?: string;
typeOnlySpecifiers: Map<string, string>; // name -> alias
isBareImport?: boolean;
}>();

function visit(node: ts.Node) {
if (ts.isImportDeclaration(node)) {
Expand All @@ -285,47 +282,54 @@ export const removeDuplicateImports = (sourceCode: string): string => {

if (!imports.has(source)) {
imports.set(source, {
specifiers: new Set(),
typeOnlySpecifiers: new Set(),
specifiers: new Map(),
typeOnlySpecifiers: new Map(),
isBareImport: !node.importClause,
});
}

// Handle bare imports
if (!node.importClause) {
imports.get(source)!.isBareImport = true;
return;
}

if (node.importClause) {
const isTypeOnly = node.importClause.isTypeOnly;

if (
node.importClause.namedBindings &&
ts.isNamespaceImport(node.importClause.namedBindings)
) {
imports.get(source)!.namespaceImport =
node.importClause.namedBindings.name.text;
} else if (
node.importClause.namedBindings &&
ts.isNamedImports(node.importClause.namedBindings)
) {
node.importClause.namedBindings.elements.forEach((element) => {
if (isTypeOnly || element.isTypeOnly) {
imports.get(source)?.typeOnlySpecifiers.add(element.name.text);
const isTypeOnly = node.importClause.isTypeOnly;

if (
node.importClause.namedBindings &&
ts.isNamespaceImport(node.importClause.namedBindings)
) {
imports.get(source)!.namespaceImport =
node.importClause.namedBindings.name.text;
} else if (
node.importClause.namedBindings &&
ts.isNamedImports(node.importClause.namedBindings)
) {
node.importClause.namedBindings.elements.forEach((element) => {
const name = element.propertyName?.text || element.name.text;
const alias = element.propertyName ? element.name.text : undefined;

if (isTypeOnly || element.isTypeOnly) {
if (alias) {
imports.get(source)?.typeOnlySpecifiers.set(name, alias);
} else {
imports.get(source)?.specifiers.add(element.name.text);
imports.get(source)?.typeOnlySpecifiers.set(name, name);
}
});
}
if (node.importClause.name) {
imports.get(source)!.defaultName = node.importClause.name.text;
if (isTypeOnly) {
imports.get(source)?.typeOnlySpecifiers.add("default");
} else {
imports.get(source)?.specifiers.add("default");
if (alias) {
imports.get(source)?.specifiers.set(name, alias);
} else {
imports.get(source)?.specifiers.set(name, name);
}
}
}
});
}
if (node.importClause.name) {
imports.get(source)!.defaultName = node.importClause.name.text;
const target = isTypeOnly
? imports.get(source)?.typeOnlySpecifiers
: imports.get(source)?.specifiers;
target?.set("default", "default");
}
}
ts.forEachChild(node, visit);
Expand All @@ -346,29 +350,32 @@ export const removeDuplicateImports = (sourceCode: string): string => {
},
] of imports
) {
// Handle bare imports first
if (isBareImport) {
newImports.push(`import '${source}';`);
continue;
}

// Handle type-only imports
if (typeOnlySpecifiers.size > 0) {
let importStr = "import type ";
const namedImports = Array.from(typeOnlySpecifiers).sort().join(", ");
importStr += `{ ${namedImports} }`;
importStr += ` from '${source}';`;
let importStr = "import type { ";
const namedImports = Array.from(typeOnlySpecifiers.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, alias]) => name === alias ? name : `${name} as ${alias}`)
.join(", ");
importStr += namedImports;
importStr += ` } from '${source}';`;
newImports.push(importStr);
}

// Handle regular imports
if (specifiers.size > 0 || namespaceImport) {
let importStr = "import ";
if (namespaceImport) {
importStr += `* as ${namespaceImport}`;
} else {
const hasDefault = specifiers.delete("default");
const namedImports = Array.from(specifiers).sort().join(", ");
const namedImports = Array.from(specifiers.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, alias]) => name === alias ? name : `${name} as ${alias}`)
.join(", ");

if (hasDefault && defaultName) {
importStr += `${defaultName} `;
Expand Down
Loading

0 comments on commit ece640d

Please sign in to comment.