@esportsplus/typescript 0.29.5 → 0.29.6

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.
@@ -92,7 +92,8 @@ function modify(code, file, pkg, options) {
92
92
  for (let i = 0, n = found.length; i < n; i++) {
93
93
  for (let [name, alias] of found[i].specifiers) {
94
94
  if (!remove || (!remove.has(name) && !remove.has(alias))) {
95
- specifiers.add(name === alias ? name : `${name} as ${alias}`);
95
+ let base = name === alias ? name : `${name} as ${alias}`;
96
+ specifiers.add(found[i].typeOnly.has(name) ? `type ${base}` : base);
96
97
  }
97
98
  }
98
99
  }
@@ -2,6 +2,7 @@ import { ts } from '../index.js';
2
2
  type ImportInfo = {
3
3
  end: number;
4
4
  specifiers: Map<string, string>;
5
+ typeOnly: Set<string>;
5
6
  start: number;
6
7
  };
7
8
  type ModifyOptions = {
@@ -15,14 +15,17 @@ const all = (file, pkg) => {
15
15
  if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== pkg) {
16
16
  continue;
17
17
  }
18
- let bindings = stmt.importClause?.namedBindings, specifiers = new Map();
18
+ let bindings = stmt.importClause?.namedBindings, declTypeOnly = stmt.importClause?.isTypeOnly ?? false, specifiers = new Map(), typeOnly = new Set();
19
19
  if (bindings && ts.isNamedImports(bindings)) {
20
20
  for (let j = 0, m = bindings.elements.length; j < m; j++) {
21
21
  let element = bindings.elements[j], name = element.name.text, propertyName = element.propertyName?.text || name;
22
22
  specifiers.set(propertyName, name);
23
+ if (declTypeOnly || element.isTypeOnly) {
24
+ typeOnly.add(propertyName);
25
+ }
23
26
  }
24
27
  }
25
- imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file) });
28
+ imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file), typeOnly });
26
29
  }
27
30
  return imports;
28
31
  };
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "type": "module",
45
45
  "types": "build/index.d.ts",
46
- "version": "0.29.5",
46
+ "version": "0.29.6",
47
47
  "scripts": {
48
48
  "bench:run": "vitest bench --run",
49
49
  "build": "tsc && tsc-alias",
@@ -0,0 +1,6 @@
1
+ # Approve dependency build scripts (pnpm errors on unapproved ones during install/publish) and trust
2
+ # our own scope so freshly published first-party deps bypass the minimumReleaseAge gate.
3
+ allowBuilds:
4
+ esbuild: true
5
+ minimumReleaseAgeExclude:
6
+ - '@esportsplus/*'
@@ -136,7 +136,12 @@ function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyO
136
136
  for (let i = 0, n = found.length; i < n; i++) {
137
137
  for (let [name, alias] of found[i].specifiers) {
138
138
  if (!remove || (!remove.has(name) && !remove.has(alias))) {
139
- specifiers.add(name === alias ? name : `${name} as ${alias}`);
139
+ let base = name === alias ? name : `${name} as ${alias}`;
140
+
141
+ // Preserve type-only specifiers with an inline `type` modifier so the merged import
142
+ // stays erasable — otherwise `import type { X }` re-emits as a runtime `import { X }`
143
+ // and fails at load time when X has no runtime export.
144
+ specifiers.add(found[i].typeOnly.has(name) ? `type ${base}` : base);
140
145
  }
141
146
  }
142
147
  }
@@ -4,6 +4,10 @@ import { ts } from '~/index';
4
4
  type ImportInfo = {
5
5
  end: number;
6
6
  specifiers: Map<string, string>;
7
+ // propertyName keys that were imported type-only, whether via a type-only clause
8
+ // (`import type { A }`) or an inline specifier (`import { type A }`). Preserved so a rewrite
9
+ // re-emits them as type imports instead of runtime imports.
10
+ typeOnly: Set<string>;
7
11
  start: number;
8
12
  };
9
13
 
@@ -43,7 +47,9 @@ const all = (file: ts.SourceFile, pkg: string): ImportInfo[] => {
43
47
  }
44
48
 
45
49
  let bindings = stmt.importClause?.namedBindings,
46
- specifiers = new Map<string, string>();
50
+ declTypeOnly = stmt.importClause?.isTypeOnly ?? false,
51
+ specifiers = new Map<string, string>(),
52
+ typeOnly = new Set<string>();
47
53
 
48
54
  if (bindings && ts.isNamedImports(bindings)) {
49
55
  for (let j = 0, m = bindings.elements.length; j < m; j++) {
@@ -52,10 +58,14 @@ const all = (file: ts.SourceFile, pkg: string): ImportInfo[] => {
52
58
  propertyName = element.propertyName?.text || name;
53
59
 
54
60
  specifiers.set(propertyName, name);
61
+
62
+ if (declTypeOnly || element.isTypeOnly) {
63
+ typeOnly.add(propertyName);
64
+ }
55
65
  }
56
66
  }
57
67
 
58
- imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file) });
68
+ imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file), typeOnly });
59
69
  }
60
70
 
61
71
  return imports;
@@ -346,6 +346,23 @@ describe('coordinator.transform', () => {
346
346
  expect(result.code).not.toMatch(/import\s*\{[^}]*reactive[^}]*\}\s*from\s*'my-pkg'/);
347
347
  });
348
348
 
349
+ it('preserves a type-only import when rewriting a package\'s imports', () => {
350
+ let code = "import { html } from 'my-pkg';\nimport type { Renderable } from 'my-pkg';\nlet x = 1;",
351
+ file = parse(code),
352
+ program = makeProgram(file),
353
+ plugin = makePlugin(() => ({
354
+ imports: [{ namespace: 'NS', package: 'my-pkg', remove: ['html'] }]
355
+ })),
356
+ result = coordinator.transform([plugin], code, file, program, '/root', new Map());
357
+
358
+ expect(result.changed).toBe(true);
359
+ expect(result.code).toContain("import * as NS from 'my-pkg';");
360
+ // Renderable must stay type-only (import type { ... } or inline `type Renderable`) so it is
361
+ // erased at runtime — a runtime `import { Renderable }` fails when it has no runtime export.
362
+ expect(result.code).toMatch(/import\s+type\s*\{[^}]*Renderable|import\s*\{[^}]*type\s+Renderable/);
363
+ expect(result.code).not.toMatch(/import\s*\{\s*Renderable\s*\}\s*from\s*'my-pkg'/);
364
+ });
365
+
349
366
  // F-TEST-003: Import manipulation integration tests
350
367
 
351
368
  it('adds specifiers to existing import', () => {