@eliasku/ts-transformers 0.0.6 → 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.6",
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,27 +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
- if (ts.isImportSpecifier(node)) {
55
- const removed = tryRemoveConstEnumImport(node);
56
- if (removed === undefined || node.isTypeOnly) {
57
- return undefined;
58
- }
59
- }
60
-
61
- if (ts.isExportSpecifier(node)) {
62
- const removed = tryRemoveConstEnumExport(node);
63
- if (removed === undefined || node.isTypeOnly) {
64
- return undefined;
65
- }
66
- }
67
-
68
- if (ts.isImportClause(node)) {
69
- const removed = tryRemoveConstEnumImportClause(node);
70
- if (removed === undefined) {
71
- return undefined;
51
+ if (inlined) {
52
+ ts.setOriginalNode(inlined, node);
53
+ return inlined;
72
54
  }
73
55
  }
74
56
  }
@@ -575,7 +557,6 @@ function createTransformerFactory(
575
557
  return putToCache(nodeSymbol, VisibilityType.External);
576
558
  }
577
559
 
578
- // TODO: mute enum renames
579
560
  if (ts.isEnumDeclaration(currentNode)) {
580
561
  return putToCache(nodeSymbol, VisibilityType.External);
581
562
  }
@@ -667,39 +648,22 @@ function createTransformerFactory(
667
648
 
668
649
  const memberValue = enumInfo.members.get(node.name.text)?.value;
669
650
  if (memberValue === undefined || memberValue === null) return null;
651
+ const newNode = enumEvaluator.createLiteral(memberValue);
670
652
 
671
- return enumEvaluator.createLiteral(memberValue);
672
- }
673
-
674
- function tryRemoveConstEnumImport(node: ts.ImportSpecifier): ts.ImportSpecifier | undefined {
675
- const importedType = typeChecker.getTypeAtLocation(node);
676
- if (isConstEnumType(importedType)) {
677
- return undefined;
678
- }
679
- return node;
680
- }
681
-
682
- function tryRemoveConstEnumExport(node: ts.ExportSpecifier): ts.ExportSpecifier | undefined {
683
- const importedType = typeChecker.getTypeAtLocation(node);
684
- if (isConstEnumType(importedType)) {
685
- return undefined;
686
- }
687
- return node;
688
- }
689
-
690
- function tryRemoveConstEnumImportClause(node: ts.ImportClause): ts.ImportClause | undefined {
691
- if (!node.name) return node;
692
- const type = typeChecker.getTypeAtLocation(node.name);
693
- if (isConstEnumType(type)) {
694
- return undefined;
695
- }
696
- 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;
697
657
  }
698
658
 
699
659
  return (sourceFile: ts.SourceFile) => {
700
660
  function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
701
- if (fullOptions.inlineConstEnums === false) return node;
702
- 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
+ }
703
667
 
704
668
  if (sourceFile.isDeclarationFile) {
705
669
  return ts.factory.updateEnumDeclaration(
@@ -709,16 +673,30 @@ function createTransformerFactory(
709
673
  node.members,
710
674
  );
711
675
  }
712
- return undefined;
676
+
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;
713
691
  }
714
692
 
715
693
  function wrapTransformNode(node: ts.Node): ts.Node | undefined {
716
694
  if (ts.isEnumDeclaration(node)) {
717
695
  const result = handleEnumDeclaration(node);
718
- if (result === undefined) {
719
- return undefined;
720
- }
721
696
  if (result !== node) {
697
+ if (result) {
698
+ return transformNode(result);
699
+ }
722
700
  return result;
723
701
  }
724
702
  }
@@ -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;