@eliasku/ts-transformers 0.0.7 → 0.0.8

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.
package/README.md CHANGED
@@ -52,7 +52,7 @@ class A{publicApi(){},a(){},b=1}
52
52
 
53
53
  ### Const Enum Inlining
54
54
 
55
- Replaces const enum accesses with literal values and removes declarations.
55
+ Replaces const enum accesses with literal values and transforms declarations to remove codegen, but keep symbol to preserve default import/export flow.
56
56
 
57
57
  ```typescript
58
58
  // Before
@@ -60,10 +60,14 @@ const enum Status {
60
60
  Active = 1,
61
61
  Inactive = 0,
62
62
  }
63
- const status = Status.Active;
63
+ export const status = Status.Active;
64
64
 
65
- // After transformer + minifier
66
- const status = 1;
65
+ // After transformer
66
+ export let Status: {};
67
+ export const status = 1;
68
+
69
+ // After minifier
70
+ export const status=1;
67
71
  ```
68
72
 
69
73
  ## Usage
@@ -79,11 +83,16 @@ const bundle = await rollup({
79
83
  input: "./src/index.ts",
80
84
  plugins: [
81
85
  typescript({
86
+ compilerOptions: {
87
+ target: "ESNext",
88
+ module: "ESNext",
89
+ moduleResolution: "Bundler",
90
+ lib: ["DOM", "ESNext"],
91
+ },
82
92
  transformers: (program) => ({
83
93
  before: [
84
94
  optimizer(program, {
85
- entrySourceFiles: ["./src/index.ts"],
86
- inlineConstEnums: true,
95
+ entrySourceFiles: ["src/index.ts"],
87
96
  }),
88
97
  ],
89
98
  }),
@@ -107,6 +116,8 @@ await build({
107
116
  });
108
117
  ```
109
118
 
119
+ **Note**: This plugin uses `@rollup/plugin-typescript`. The transformer receives a TypeScript `Program` object directly.
120
+
110
121
  ## Options
111
122
 
112
123
  ### entrySourceFiles (required)
@@ -156,7 +167,7 @@ class AppComponent {
156
167
 
157
168
  ### inlineConstEnums (optional, default: true)
158
169
 
159
- Inline const enum values and remove declarations.
170
+ Inline const enum values and transform declarations to preserve export/import flow.
160
171
 
161
172
  ## Complete Example
162
173
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eliasku/ts-transformers",
3
3
  "description": "TypeScript transformer for code optimization",
4
- "version": "0.0.7",
4
+ "version": "0.0.8",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -2,7 +2,7 @@ import ts from "typescript";
2
2
  import { EnumValue, EvaluationContext } from "./evaluator";
3
3
  import { EnumEvaluator } from "./evaluator";
4
4
  import { isConstEnumSymbol } from "./utils";
5
- import { hasModifier } from "../typescript-helpers";
5
+ import { hasModifier } from "../utils/symbol-utils";
6
6
 
7
7
  export interface ConstEnumInfo {
8
8
  declaration: ts.EnumDeclaration;
package/src/index.ts CHANGED
@@ -48,30 +48,9 @@ function createTransformerFactory(
48
48
  if (fullOptions.inlineConstEnums !== false) {
49
49
  if (ts.isPropertyAccessExpression(node)) {
50
50
  const inlined = tryInlineConstEnum(node);
51
- if (inlined) return inlined;
52
- }
53
-
54
- // don't remove any imports
55
- if (fullOptions.keepConstEnumEmptyDeclaration !== true) {
56
- if (ts.isImportSpecifier(node)) {
57
- const removed = tryRemoveConstEnumImport(node);
58
- if (removed === undefined || node.isTypeOnly) {
59
- return undefined;
60
- }
61
- }
62
-
63
- if (ts.isExportSpecifier(node)) {
64
- const removed = tryRemoveConstEnumExport(node);
65
- if (removed === undefined || node.isTypeOnly) {
66
- return undefined;
67
- }
68
- }
69
-
70
- if (ts.isImportClause(node)) {
71
- const removed = tryRemoveConstEnumImportClause(node);
72
- if (removed === undefined) {
73
- return undefined;
74
- }
51
+ if (inlined) {
52
+ ts.setOriginalNode(inlined, node);
53
+ return inlined;
75
54
  }
76
55
  }
77
56
  }
@@ -578,7 +557,6 @@ function createTransformerFactory(
578
557
  return putToCache(nodeSymbol, VisibilityType.External);
579
558
  }
580
559
 
581
- // TODO: mute enum renames
582
560
  if (ts.isEnumDeclaration(currentNode)) {
583
561
  return putToCache(nodeSymbol, VisibilityType.External);
584
562
  }
@@ -670,39 +648,22 @@ function createTransformerFactory(
670
648
 
671
649
  const memberValue = enumInfo.members.get(node.name.text)?.value;
672
650
  if (memberValue === undefined || memberValue === null) return null;
651
+ const newNode = enumEvaluator.createLiteral(memberValue);
673
652
 
674
- return enumEvaluator.createLiteral(memberValue);
675
- }
676
-
677
- function tryRemoveConstEnumImport(node: ts.ImportSpecifier): ts.ImportSpecifier | undefined {
678
- const importedType = typeChecker.getTypeAtLocation(node);
679
- if (isConstEnumType(importedType)) {
680
- return undefined;
681
- }
682
- return node;
683
- }
684
-
685
- function tryRemoveConstEnumExport(node: ts.ExportSpecifier): ts.ExportSpecifier | undefined {
686
- const importedType = typeChecker.getTypeAtLocation(node);
687
- if (isConstEnumType(importedType)) {
688
- return undefined;
689
- }
690
- return node;
691
- }
692
-
693
- function tryRemoveConstEnumImportClause(node: ts.ImportClause): ts.ImportClause | undefined {
694
- if (!node.name) return node;
695
- const type = typeChecker.getTypeAtLocation(node.name);
696
- if (isConstEnumType(type)) {
697
- return undefined;
698
- }
699
- return node;
653
+ const r = ts.setOriginalNode(newNode, node);
654
+ ts.setSourceMapRange(newNode, ts.getSourceMapRange(node));
655
+ (newNode as any).symbol = (node as any).symbol; // eslint-disable-line @typescript-eslint/no-explicit-any
656
+ return r;
700
657
  }
701
658
 
702
659
  return (sourceFile: ts.SourceFile) => {
703
660
  function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
704
- if (fullOptions.inlineConstEnums === false) return node;
705
- if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) return node;
661
+ if (fullOptions.inlineConstEnums === false) {
662
+ return node;
663
+ }
664
+ if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
665
+ return node;
666
+ }
706
667
 
707
668
  if (sourceFile.isDeclarationFile) {
708
669
  return ts.factory.updateEnumDeclaration(
@@ -713,10 +674,20 @@ function createTransformerFactory(
713
674
  );
714
675
  }
715
676
 
716
- if (fullOptions.keepConstEnumEmptyDeclaration === true) {
717
- return ts.factory.updateEnumDeclaration(node, node.modifiers, node.name, []);
718
- }
719
- return undefined;
677
+ return ts.factory.createVariableStatement(
678
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
679
+ ts.factory.createVariableDeclarationList(
680
+ [
681
+ ts.factory.createVariableDeclaration(
682
+ node.name, // Reuse enum name
683
+ undefined,
684
+ ts.factory.createTypeLiteralNode([]), // Empty object type: {}
685
+ undefined, // No initializer (empty declaration)
686
+ ),
687
+ ],
688
+ ts.NodeFlags.Let, // Sets the 'const' modifier
689
+ ),
690
+ ) as unknown as ts.EnumDeclaration;
720
691
  }
721
692
 
722
693
  function wrapTransformNode(node: ts.Node): ts.Node | undefined {
package/src/types.ts CHANGED
@@ -32,11 +32,6 @@ export interface OptimizerOptions {
32
32
  * Whether to inline const enum values.
33
33
  */
34
34
  inlineConstEnums?: boolean;
35
-
36
- /**
37
- * Keep empty const enum declarations, keep imports / exports
38
- */
39
- keepConstEnumEmptyDeclaration?: boolean;
40
35
  }
41
36
 
42
37
  export const enum VisibilityType {
@@ -1,204 +0,0 @@
1
- import ts from "typescript";
2
-
3
- const namedDeclarationKinds = [
4
- ts.SyntaxKind.InterfaceDeclaration,
5
- ts.SyntaxKind.ClassDeclaration,
6
- ts.SyntaxKind.EnumDeclaration,
7
- ts.SyntaxKind.TypeAliasDeclaration,
8
- ts.SyntaxKind.ModuleDeclaration,
9
- ts.SyntaxKind.FunctionDeclaration,
10
- ts.SyntaxKind.VariableDeclaration,
11
- ts.SyntaxKind.PropertySignature,
12
- ts.SyntaxKind.Parameter,
13
- ];
14
-
15
- export const isNodeNamedDeclaration = (node: ts.Node): node is ts.NamedDeclaration =>
16
- namedDeclarationKinds.indexOf(node.kind) !== -1;
17
-
18
- export function getActualSymbol(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol {
19
- if (symbol.flags & ts.SymbolFlags.Alias) {
20
- symbol = typeChecker.getAliasedSymbol(symbol);
21
- }
22
-
23
- return symbol;
24
- }
25
-
26
- export function splitTransientSymbol(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol[] {
27
- // actually I think we even don't need to operate/use "Transient" symbols anywhere
28
- // it's kind of aliased symbol, but just merged
29
- // but it's hard to refractor everything to use array of symbols instead of just symbol
30
- // so let's fix it for some places
31
- if ((symbol.flags & ts.SymbolFlags.Transient) === 0) {
32
- return [symbol];
33
- }
34
-
35
- // "Transient" symbol is kinda "merged" symbol
36
- // I don't really know is this way to "split" is correct
37
- // but it seems that it works for now ¯\_(ツ)_/¯
38
- const declarations = getDeclarationsForSymbol(symbol);
39
- const result: ts.Symbol[] = [];
40
- for (const declaration of declarations) {
41
- if (!isNodeNamedDeclaration(declaration) || declaration.name === undefined) {
42
- continue;
43
- }
44
-
45
- const sym = typeChecker.getSymbolAtLocation(declaration.name);
46
- if (sym === undefined) {
47
- continue;
48
- }
49
-
50
- result.push(getActualSymbol(sym, typeChecker));
51
- }
52
-
53
- return result;
54
- }
55
-
56
- export function getDeclarationsForSymbol(symbol: ts.Symbol): ts.Declaration[] {
57
- const result: ts.Declaration[] = [];
58
-
59
- if (symbol.declarations !== undefined) {
60
- result.push(...symbol.declarations);
61
- }
62
-
63
- if (symbol.valueDeclaration !== undefined) {
64
- // push valueDeclaration might be already in declarations array
65
- // so let's check first to avoid duplication nodes
66
- if (!result.includes(symbol.valueDeclaration)) {
67
- result.push(symbol.valueDeclaration);
68
- }
69
- }
70
-
71
- return result;
72
- }
73
-
74
- export function getExportsForSourceFile(typeChecker: ts.TypeChecker, sourceFileSymbol: ts.Symbol): ts.Symbol[] {
75
- if (sourceFileSymbol.exports !== undefined) {
76
- const commonJsExport = sourceFileSymbol.exports.get(ts.InternalSymbolName.ExportEquals);
77
- if (commonJsExport !== undefined) {
78
- return [getActualSymbol(commonJsExport, typeChecker)];
79
- }
80
- }
81
-
82
- const result: ts.Symbol[] = typeChecker.getExportsOfModule(sourceFileSymbol);
83
-
84
- if (sourceFileSymbol.exports !== undefined) {
85
- const defaultExportSymbol = sourceFileSymbol.exports.get(ts.InternalSymbolName.Default);
86
- if (defaultExportSymbol !== undefined) {
87
- if (!result.includes(defaultExportSymbol)) {
88
- // it seems that default export is always returned by getExportsOfModule
89
- // but let's add it to be sure add if there is no such export
90
- result.push(defaultExportSymbol);
91
- }
92
- }
93
- }
94
-
95
- return result.map((symbol: ts.Symbol) => getActualSymbol(symbol, typeChecker));
96
- }
97
-
98
- export type ClassMember =
99
- | ts.MethodDeclaration
100
- | ts.PropertyDeclaration
101
- | ts.GetAccessorDeclaration
102
- | ts.SetAccessorDeclaration;
103
-
104
- export const isClassMember = (node: ts.Node): node is ClassMember =>
105
- ts.isMethodDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node);
106
-
107
- export function getClassOfMemberSymbol(
108
- nodeSymbol: ts.Symbol,
109
- ): ts.ClassLikeDeclaration | ts.ObjectLiteralExpression | ts.TypeLiteralNode | ts.InterfaceDeclaration | null {
110
- const classMembers = getClassMemberDeclarations(nodeSymbol);
111
- if (classMembers.length !== 0) {
112
- // we need any member to get class' declaration
113
- const classMember = classMembers[0];
114
- if (isConstructorParameter(classMember)) {
115
- return (classMember.parent as ts.ConstructorDeclaration).parent;
116
- }
117
-
118
- // we're sure that it is a class, not interface
119
- return classMember.parent as ts.ClassLikeDeclaration | ts.ObjectLiteralExpression;
120
- }
121
-
122
- return null;
123
- }
124
-
125
- export const hasPrivateKeyword = (node: ClassMember | ts.ParameterDeclaration) =>
126
- hasModifier(node, ts.SyntaxKind.PrivateKeyword);
127
-
128
- function getModifiers(node: ts.Node): readonly ts.Modifier[] {
129
- if (isBreakingTypeScriptApi(ts)) {
130
- if (!ts.canHaveModifiers(node)) {
131
- return [];
132
- }
133
-
134
- return ts.getModifiers(node) || [];
135
- }
136
-
137
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
138
- // @ts-ignore
139
- return node.modifiers || [];
140
- }
141
-
142
- export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind): boolean =>
143
- ts.canHaveModifiers(node) && getModifiers(node)?.some((mod) => mod.kind === modifier);
144
-
145
- function getDecorators(node: ts.Node): readonly unknown[] {
146
- if (isBreakingTypeScriptApi(ts)) {
147
- if (!ts.canHaveDecorators(node)) {
148
- return [];
149
- }
150
-
151
- return ts.getDecorators(node) || [];
152
- }
153
-
154
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
155
- // @ts-ignore
156
- return node.decorators || [];
157
- }
158
-
159
- export const hasDecorators = (node: ts.Node) => getDecorators(node).length !== 0;
160
-
161
- export const isConstructorParameter = (node: ts.Node): node is ts.ParameterDeclaration =>
162
- ts.isParameter(node) &&
163
- ts.isConstructorDeclaration(node.parent as ts.Node) &&
164
- (hasModifier(node, ts.SyntaxKind.PublicKeyword) ||
165
- hasModifier(node, ts.SyntaxKind.ProtectedKeyword) ||
166
- hasModifier(node, ts.SyntaxKind.PrivateKeyword) ||
167
- hasModifier(node, ts.SyntaxKind.ReadonlyKeyword));
168
-
169
- function getClassMemberDeclarations(symbol: ts.Symbol | undefined): (ClassMember | ts.ParameterDeclaration)[] {
170
- if (symbol === undefined) {
171
- return [];
172
- }
173
-
174
- const declarations = symbol.getDeclarations();
175
- if (declarations === undefined) {
176
- return [];
177
- }
178
-
179
- return declarations.filter((x: ts.Declaration): x is ClassMember | ts.ParameterDeclaration => {
180
- return isClassMember(x) || isConstructorParameter(x);
181
- });
182
- }
183
-
184
- export const isSymbolClassMember = (symbol: ts.Symbol | undefined) => getClassMemberDeclarations(symbol).length !== 0;
185
-
186
- export const isPrivateClassMember = (symbol: ts.Symbol | undefined) =>
187
- getClassMemberDeclarations(symbol).some(hasPrivateKeyword);
188
-
189
- export function getNodeJSDocComment(node: ts.Node): string {
190
- const start = node.getStart();
191
- const jsDocStart = node.getStart(undefined, true);
192
- return node.getSourceFile().getFullText().substring(jsDocStart, start).trim();
193
- }
194
-
195
- // decorators and modifiers-related api added in ts 4.8
196
- interface BreakingTypeScriptApi {
197
- canHaveDecorators(node: ts.Node): boolean;
198
- getDecorators(node: ts.Node): readonly ts.Decorator[] | undefined;
199
- canHaveModifiers(node: ts.Node): boolean;
200
- getModifiers(node: ts.Node): readonly ts.Modifier[] | undefined;
201
- }
202
-
203
- const isBreakingTypeScriptApi = (compiler: object): compiler is BreakingTypeScriptApi =>
204
- "canHaveDecorators" in compiler;