@eliasku/ts-transformers 0.0.1 → 0.0.2

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
@@ -0,0 +1,44 @@
1
+ # @eliasku/ts-transformers
2
+
3
+ TypeScript transformer for code optimization.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ import { optimizer } from "@eliasku/ts-transformers";
9
+
10
+ transformers: (program) => ({
11
+ before: [
12
+ optimizer(program, {
13
+ entrySourceFiles: ["./src/index.ts"],
14
+ inlineConstEnums: true,
15
+ }),
16
+ ],
17
+ });
18
+ ```
19
+
20
+ ## Options
21
+
22
+ ### entrySourceFiles (required)
23
+
24
+ An array of entry source files which will be used to detect exported and internal fields. Basically it should be entry point(s) of the library/project.
25
+
26
+ ### inlineConstEnums (optional, default: true)
27
+
28
+ Whether to inline const enum values and remove const enum declarations.
29
+
30
+ ### privatePrefix (optional, default: "$p$")
31
+
32
+ Prefix of generated names for private fields.
33
+
34
+ ### internalPrefix (optional, default: "$i$")
35
+
36
+ Prefix of generated names for internal fields.
37
+
38
+ ### publicJSDocTag (optional, default: "public")
39
+
40
+ Comment which will treat a class/interface/type/property/etc and all its children as "public". Set it to empty string to disable using JSDoc comment to detect "visibility level".
41
+
42
+ ### ignoreDecorated (optional, default: false)
43
+
44
+ Whether fields that were decorated should be renamed. A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eliasku/ts-transformers",
3
- "description": "",
4
- "version": "0.0.1",
3
+ "description": "TypeScript transformer for code optimization",
4
+ "version": "0.0.2",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -22,9 +22,10 @@
22
22
  "@rollup/plugin-typescript": "latest",
23
23
  "tslib": "latest"
24
24
  },
25
+ "module": "./src/index.ts",
26
+ "types": "./src/index.ts",
25
27
  "exports": {
26
- "./mangler": "./src/mangler/index.ts",
27
- "./const-enum": "./src/const-enum/index.ts"
28
+ ".": "./src/index.ts"
28
29
  },
29
30
  "repository": {
30
31
  "type": "git",
@@ -58,7 +58,6 @@ export class EnumEvaluator {
58
58
 
59
59
  private evaluateImplicitMember(member: ts.EnumMember): EnumValue {
60
60
  const name = ts.isIdentifier(member.name) ? member.name.text : `<computed>`;
61
- // unused
62
61
  void name;
63
62
 
64
63
  if (this.lastImplicitValue === -1) {
@@ -104,12 +103,10 @@ export class EnumEvaluator {
104
103
  const left = this.evaluate(expr.left, context);
105
104
  const right = this.evaluate(expr.right, context);
106
105
 
107
- // String concatenation
108
106
  if (typeof left === "string" && typeof right === "string" && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
109
107
  return left + right;
110
108
  }
111
109
 
112
- // Numeric operations
113
110
  if (typeof left === "number" && typeof right === "number") {
114
111
  switch (expr.operatorToken.kind) {
115
112
  case ts.SyntaxKind.BarToken:
@@ -2,7 +2,6 @@ import ts from "typescript";
2
2
  import { EnumValue, EvaluationContext } from "./evaluator";
3
3
  import { EnumEvaluator } from "./evaluator";
4
4
  import { hasModifier, isConstEnumSymbol } from "./utils";
5
- import { LOGS } from "../config";
6
5
 
7
6
  export interface ConstEnumInfo {
8
7
  declaration: ts.EnumDeclaration;
@@ -56,29 +55,15 @@ export class ConstEnumRegistry {
56
55
  }
57
56
 
58
57
  private collectConstEnumsFromEntryPoints(): void {
59
- if (LOGS) {
60
- console.log(`[const-enum registry] Starting collection from ${this.entrySourceFiles.length} entry point(s)`);
61
- }
62
-
63
- // Collect all const enums from the entire program
64
58
  const sourceFiles = this.program.getSourceFiles();
65
- if (LOGS) {
66
- console.log(`[const-enum registry] Program has ${sourceFiles.length} source files`);
67
- }
68
59
 
69
60
  for (const sourceFile of sourceFiles) {
70
- // We are using typescript files from node_modules as well, so don't skip them
71
- // but skip declaration files
72
61
  if (sourceFile.isDeclarationFile) {
73
62
  continue;
74
63
  }
75
64
 
76
65
  this.registerConstEnumFromSource(sourceFile);
77
66
  }
78
-
79
- if (LOGS) {
80
- console.log(`[const-enum registry] Found ${this.enumDeclarations.size} const enum declarations`);
81
- }
82
67
  }
83
68
 
84
69
  private registerConstEnumFromSource(sourceFile: ts.SourceFile): void {
@@ -96,7 +81,6 @@ export class ConstEnumRegistry {
96
81
  const name = this.getEnumSymbolName(symbol);
97
82
 
98
83
  if (this.enumDeclarations.has(name)) {
99
- // Already registered (might be from different import)
100
84
  return;
101
85
  }
102
86
 
@@ -8,7 +8,7 @@ import {
8
8
  getExportsForSourceFile,
9
9
  getDeclarationsForSymbol,
10
10
  } from "../utils/symbol-utils";
11
- import { LOGS } from "../../config";
11
+ import { LOGS } from "../config";
12
12
 
13
13
  export class ExportsSymbolTree {
14
14
  private readonly program: ts.Program;
@@ -14,23 +14,26 @@ import {
14
14
  isNodeNamedDeclaration,
15
15
  } from "./utils/symbol-utils";
16
16
  import { getNodeJSDocComment } from "./utils/ast-utils";
17
- import { RenameOptions, defaultOptions, VisibilityType } from "./types";
18
- import { LOGS } from "../config";
17
+ import { OptimizerOptions, defaultOptions, VisibilityType } from "./types";
18
+ import { ConstEnumRegistry } from "./const-enum/registry";
19
+ import { EnumEvaluator } from "./const-enum/evaluator";
20
+ import { isConstEnumType } from "./const-enum/utils";
21
+ import { LOGS } from "./config";
19
22
 
20
- export function propertiesRenameTransformer(
23
+ export const optimizer = (
21
24
  program: ts.Program,
22
- config?: Partial<RenameOptions>,
23
- ): ts.TransformerFactory<ts.SourceFile> {
24
- return createTransformerFactory(program, config);
25
- }
25
+ config?: Partial<OptimizerOptions>,
26
+ ): ts.TransformerFactory<ts.SourceFile> => createTransformerFactory(program, config);
26
27
 
27
28
  function createTransformerFactory(
28
29
  program: ts.Program,
29
- options?: Partial<RenameOptions>,
30
+ options?: Partial<OptimizerOptions>,
30
31
  ): ts.TransformerFactory<ts.SourceFile> {
31
- const fullOptions: RenameOptions = { ...defaultOptions, ...options };
32
+ const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
32
33
  const typeChecker = program.getTypeChecker();
33
34
  const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
35
+ const constEnumRegistry = new ConstEnumRegistry(program, fullOptions.entrySourceFiles);
36
+ const enumEvaluator = new EnumEvaluator(typeChecker);
34
37
 
35
38
  const cache = new Map<ts.Symbol, VisibilityType>();
36
39
 
@@ -40,17 +43,24 @@ function createTransformerFactory(
40
43
  }
41
44
 
42
45
  return (context: ts.TransformationContext) => {
43
- function transformNodeAndChildren(node: ts.SourceFile, ctx: ts.TransformationContext): ts.SourceFile;
44
- function transformNodeAndChildren(node: ts.Node, ctx: ts.TransformationContext): ts.Node;
45
- function transformNodeAndChildren(node: ts.Node, ctx: ts.TransformationContext): ts.Node {
46
- return ts.visitEachChild(
47
- transformNode(node),
48
- (childNode: ts.Node) => transformNodeAndChildren(childNode, ctx),
49
- ctx,
50
- );
51
- }
52
-
53
46
  function transformNode(node: ts.Node): ts.Node {
47
+ if (fullOptions.inlineConstEnums !== false) {
48
+ if (ts.isPropertyAccessExpression(node)) {
49
+ const inlined = tryInlineConstEnum(node);
50
+ if (inlined) return inlined;
51
+ }
52
+
53
+ if (ts.isImportSpecifier(node)) {
54
+ const removed = tryRemoveConstEnumImport(node);
55
+ if (removed === undefined) return undefined;
56
+ }
57
+
58
+ if (ts.isImportClause(node)) {
59
+ const removed = tryRemoveConstEnumImportClause(node);
60
+ if (removed === undefined) return undefined;
61
+ }
62
+ }
63
+
54
64
  // const a = { node }
55
65
  if (ts.isShorthandPropertyAssignment(node)) {
56
66
  return handleShorthandPropertyAssignment(node);
@@ -637,6 +647,73 @@ function createTransformerFactory(
637
647
  return isSymbolClassMember(typeChecker.getSymbolAtLocation(node));
638
648
  }
639
649
 
640
- return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile, context);
650
+ function tryInlineConstEnum(node: ts.PropertyAccessExpression): ts.Expression | null {
651
+ const expressionType = typeChecker.getTypeAtLocation(node.expression);
652
+ if (!isConstEnumType(expressionType)) return null;
653
+
654
+ const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
655
+ if (!enumSymbol) return null;
656
+
657
+ const enumInfo = constEnumRegistry.getEnumInfo(enumSymbol);
658
+ if (!enumInfo) return null;
659
+
660
+ const memberValue = enumInfo.members.get(node.name.text)?.value;
661
+ if (memberValue === undefined || memberValue === null) return null;
662
+
663
+ return enumEvaluator.createLiteral(memberValue);
664
+ }
665
+
666
+ function tryRemoveConstEnumImport(node: ts.ImportSpecifier): ts.ImportSpecifier | undefined {
667
+ const importedType = typeChecker.getTypeAtLocation(node);
668
+ if (isConstEnumType(importedType)) {
669
+ return undefined;
670
+ }
671
+ return node;
672
+ }
673
+
674
+ function tryRemoveConstEnumImportClause(node: ts.ImportClause): ts.ImportClause | undefined {
675
+ if (!node.name) return node;
676
+ const type = typeChecker.getTypeAtLocation(node.name);
677
+ if (isConstEnumType(type)) {
678
+ return undefined;
679
+ }
680
+ return node;
681
+ }
682
+
683
+ return (sourceFile: ts.SourceFile) => {
684
+ function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
685
+ if (fullOptions.inlineConstEnums === false) return node;
686
+ if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) return node;
687
+
688
+ if (sourceFile.isDeclarationFile) {
689
+ return ts.factory.updateEnumDeclaration(
690
+ node,
691
+ node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
692
+ node.name,
693
+ node.members,
694
+ );
695
+ }
696
+ return undefined;
697
+ }
698
+
699
+ function wrapTransformNode(node: ts.Node): ts.Node {
700
+ if (ts.isEnumDeclaration(node)) {
701
+ const result = handleEnumDeclaration(node);
702
+ if (result === undefined) return undefined;
703
+ if (result !== node) return result;
704
+ }
705
+ return transformNode(node);
706
+ }
707
+
708
+ function wrappedTransformNodeAndChildren(node: ts.Node): ts.Node {
709
+ return ts.visitEachChild(
710
+ wrapTransformNode(node),
711
+ (childNode: ts.Node) => wrappedTransformNodeAndChildren(childNode),
712
+ context,
713
+ );
714
+ }
715
+
716
+ return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
717
+ };
641
718
  };
642
719
  }
@@ -1,4 +1,4 @@
1
- export interface RenameOptions {
1
+ export interface OptimizerOptions {
2
2
  /**
3
3
  * An array of entry source files which will used to detect exported and internal fields.
4
4
  * Basically it should be entry point(s) of the library/project.
@@ -34,6 +34,11 @@ export interface RenameOptions {
34
34
  * A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
35
35
  */
36
36
  ignoreDecorated: boolean;
37
+
38
+ /**
39
+ * Whether to inline const enum values.
40
+ */
41
+ inlineConstEnums?: boolean;
37
42
  }
38
43
 
39
44
  export const enum VisibilityType {
@@ -42,10 +47,11 @@ export const enum VisibilityType {
42
47
  External = 2,
43
48
  }
44
49
 
45
- export const defaultOptions: RenameOptions = {
50
+ export const defaultOptions: OptimizerOptions = {
46
51
  entrySourceFiles: [],
47
52
  privatePrefix: "$p$",
48
53
  internalPrefix: "$i$",
49
54
  publicJSDocTag: "public",
50
55
  ignoreDecorated: false,
56
+ inlineConstEnums: true,
51
57
  };
@@ -1,181 +0,0 @@
1
- import ts from "typescript";
2
- import { ConstEnumRegistry } from "./registry";
3
- import { EnumEvaluator } from "./evaluator";
4
- import { hasModifier, isConstEnumType } from "./utils";
5
- import { LOGS } from "../config";
6
-
7
- export const tsTransformConstEnums = (
8
- program: ts.Program,
9
- entrySourceFiles?: readonly string[],
10
- ): ts.TransformerFactory<ts.SourceFile> => {
11
- if (LOGS) {
12
- console.log("[const-enum] tsTransformConstEnums called!");
13
- }
14
- const startTime = performance.now();
15
- const registry = new ConstEnumRegistry(program, entrySourceFiles);
16
- const typeChecker = program.getTypeChecker();
17
- const evaluator = new EnumEvaluator(typeChecker);
18
- if (LOGS) {
19
- console.log(
20
- `[const-enum] Found ${registry.getEnumCount()} const enum declarations in ${performance.now() - startTime}ms`,
21
- );
22
- }
23
-
24
- return (context: ts.TransformationContext) => {
25
- function transformNodeAndChildren(
26
- node: ts.Node,
27
- ctx: ts.TransformationContext,
28
- sourceFile: ts.SourceFile,
29
- ): ts.Node {
30
- return ts.visitEachChild(
31
- transformNode(node, sourceFile, ctx, registry, evaluator, typeChecker),
32
- (childNode: ts.Node) => transformNodeAndChildren(childNode, ctx, sourceFile),
33
- ctx,
34
- );
35
- }
36
- return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile, context, sourceFile) as ts.SourceFile;
37
- };
38
- };
39
-
40
- function transformNode(
41
- node: ts.Node,
42
- sourceFile: ts.SourceFile,
43
- ctx: ts.TransformationContext,
44
- registry: ConstEnumRegistry,
45
- evaluator: EnumEvaluator,
46
- typeChecker: ts.TypeChecker,
47
- ): ts.Node {
48
- if (ts.isPropertyAccessExpression(node)) {
49
- return transformPropertyAccess(node, ctx, registry, evaluator, typeChecker);
50
- }
51
-
52
- if (ts.isEnumDeclaration(node)) {
53
- return transformEnumDeclaration(node, sourceFile, ctx);
54
- }
55
-
56
- if (ts.isImportSpecifier(node)) {
57
- return transformImportSpecifier(node, ctx, registry, typeChecker);
58
- }
59
-
60
- if (ts.isImportClause(node)) {
61
- return transformImportClause(node, ctx, registry, typeChecker);
62
- }
63
-
64
- return ts.visitEachChild(
65
- node,
66
- (child) => transformNode(child, sourceFile, ctx, registry, evaluator, typeChecker),
67
- ctx,
68
- );
69
- }
70
-
71
- function transformPropertyAccess(
72
- node: ts.PropertyAccessExpression,
73
- ctx: ts.TransformationContext,
74
- registry: ConstEnumRegistry,
75
- evaluator: EnumEvaluator,
76
- typeChecker: ts.TypeChecker,
77
- ): ts.Expression | ts.PropertyAccessExpression {
78
- const expressionType = typeChecker.getTypeAtLocation(node.expression);
79
-
80
- if (!isConstEnumType(expressionType)) {
81
- return node;
82
- }
83
-
84
- const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
85
- if (!enumSymbol) {
86
- return node;
87
- }
88
-
89
- const enumInfo = registry.getEnumInfo(enumSymbol);
90
- if (!enumInfo) {
91
- if (LOGS) {
92
- console.warn(`[const-enum] Could not find const enum ${enumSymbol.name}`);
93
- }
94
- return node;
95
- }
96
-
97
- const memberValue = enumInfo.members.get(node.name.text)?.value;
98
- if (memberValue === undefined || memberValue === null) {
99
- if (LOGS) {
100
- console.warn(`[const-enum] Could not find member ${enumSymbol.name}.${node.name.text}`);
101
- }
102
- return node;
103
- }
104
-
105
- const literal = evaluator.createLiteral(memberValue);
106
- if (LOGS) {
107
- console.log(`[const-enum] Inline ${enumSymbol.name}.${node.name.text} → ${JSON.stringify(memberValue)}`);
108
- }
109
-
110
- return literal;
111
- }
112
-
113
- function transformEnumDeclaration(
114
- node: ts.EnumDeclaration,
115
- sourceFile: ts.SourceFile,
116
- ctx: ts.TransformationContext,
117
- ): ts.EnumDeclaration | undefined {
118
- // unused
119
- void ctx;
120
-
121
- if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
122
- return node;
123
- }
124
-
125
- if (sourceFile.isDeclarationFile) {
126
- if (LOGS) {
127
- console.log(`[const-enum] Strip 'const' from ${node.name.text} in ${sourceFile.fileName}`);
128
- }
129
- return ts.factory.updateEnumDeclaration(
130
- node,
131
- node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
132
- node.name,
133
- node.members,
134
- );
135
- }
136
-
137
- if (LOGS) {
138
- console.log(`[const-enum] Remove const enum declaration ${node.name.text} in ${sourceFile.fileName}`);
139
- }
140
- return undefined;
141
- }
142
-
143
- function transformImportSpecifier(
144
- node: ts.ImportSpecifier,
145
- ctx: ts.TransformationContext,
146
- registry: ConstEnumRegistry,
147
- typeChecker: ts.TypeChecker,
148
- ): ts.ImportSpecifier | undefined {
149
- const importedType = typeChecker.getTypeAtLocation(node);
150
-
151
- if (isConstEnumType(importedType)) {
152
- if (LOGS) {
153
- console.log(`[const-enum] Remove import of const enum ${importedType.symbol?.name}`);
154
- }
155
- return undefined;
156
- }
157
-
158
- return node;
159
- }
160
-
161
- function transformImportClause(
162
- node: ts.ImportClause,
163
- ctx: ts.TransformationContext,
164
- registry: ConstEnumRegistry,
165
- typeChecker: ts.TypeChecker,
166
- ): ts.ImportClause | undefined {
167
- if (!node.name) {
168
- return node;
169
- }
170
-
171
- const type = typeChecker.getTypeAtLocation(node.name);
172
-
173
- if (isConstEnumType(type)) {
174
- if (LOGS) {
175
- console.log(`[const-enum] Remove import clause for const enum ${type.symbol?.name}`);
176
- }
177
- return undefined;
178
- }
179
-
180
- return node;
181
- }
File without changes
File without changes