@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 +18 -7
- package/package.json +1 -1
- package/src/const-enum/registry.ts +1 -1
- package/src/index.ts +28 -57
- package/src/types.ts +0 -5
- package/src/typescript-helpers.ts +0 -204
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
|
|
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
|
|
66
|
-
|
|
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: ["
|
|
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
|
|
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
|
@@ -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 "../
|
|
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)
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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)
|
|
705
|
-
|
|
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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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;
|