@eliasku/ts-transformers 0.0.7 → 0.0.9

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
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @eliasku/ts-transformers
5
5
 
6
- TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
6
+ TypeScript transformer for aggressive code minification through type-aware property renaming.
7
7
 
8
8
  ## Important Requirement
9
9
 
@@ -24,7 +24,7 @@ Two-phase optimization pipeline:
24
24
 
25
25
  Based on type analysis, properties are categorized as:
26
26
 
27
- - **Public (External)**: Exported from entry points → **no prefix** (preserved)
27
+ - **Public**: Exported from entry points → **no prefix** (preserved)
28
28
  - **Private**: Everything else → prefixed with `$_` (mangled by minifier)
29
29
 
30
30
  **Example:**
@@ -52,19 +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.
56
-
57
- ```typescript
58
- // Before
59
- const enum Status {
60
- Active = 1,
61
- Inactive = 0,
62
- }
63
- const status = Status.Active;
64
-
65
- // After transformer + minifier
66
- const status = 1;
67
- ```
55
+ TypeScript currently inlines const enums, if you use traditional compiler for production build. Check if you have option `isolatedModules: false` disabled in `tsconfig.json`.
68
56
 
69
57
  ## Usage
70
58
 
@@ -79,11 +67,16 @@ const bundle = await rollup({
79
67
  input: "./src/index.ts",
80
68
  plugins: [
81
69
  typescript({
70
+ compilerOptions: {
71
+ target: "ESNext",
72
+ module: "ESNext",
73
+ moduleResolution: "Bundler",
74
+ lib: ["DOM", "ESNext"],
75
+ },
82
76
  transformers: (program) => ({
83
77
  before: [
84
78
  optimizer(program, {
85
- entrySourceFiles: ["./src/index.ts"],
86
- inlineConstEnums: true,
79
+ entrySourceFiles: ["src/index.ts"],
87
80
  }),
88
81
  ],
89
82
  }),
@@ -107,6 +100,8 @@ await build({
107
100
  });
108
101
  ```
109
102
 
103
+ **Note**: This plugin uses `@rollup/plugin-typescript`. The transformer receives a TypeScript `Program` object directly.
104
+
110
105
  ## Options
111
106
 
112
107
  ### entrySourceFiles (required)
@@ -154,10 +149,6 @@ class AppComponent {
154
149
  }
155
150
  ```
156
151
 
157
- ### inlineConstEnums (optional, default: true)
158
-
159
- Inline const enum values and remove declarations.
160
-
161
152
  ## Complete Example
162
153
 
163
154
  ```typescript
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.9",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -40,7 +40,6 @@
40
40
  "plugin",
41
41
  "transformer",
42
42
  "minify",
43
- "const-enum",
44
43
  "treeshake",
45
44
  "mangle"
46
45
  ],
package/src/index.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  import ts from "typescript";
2
- export type { OptimizerOptions } from "./types";
3
-
4
2
  import { ExportsSymbolTree } from "./exports/tracker";
5
3
  import {
6
4
  getDeclarationsForSymbol,
@@ -16,25 +14,17 @@ import {
16
14
  } from "./utils/symbol-utils";
17
15
  import { getNodeJSDocComment } from "./utils/ast-utils";
18
16
  import { OptimizerOptions, defaultOptions, VisibilityType } from "./types";
19
- import { ConstEnumRegistry } from "./const-enum/registry";
20
- import { EnumEvaluator } from "./const-enum/evaluator";
21
- import { isConstEnumType } from "./const-enum/utils";
22
17
  import { LOGS } from "./config";
23
18
 
24
- export const optimizer = (
25
- program: ts.Program,
26
- config?: Partial<OptimizerOptions>,
27
- ): ts.TransformerFactory<ts.SourceFile> => createTransformerFactory(program, config);
19
+ export type { OptimizerOptions } from "./types";
28
20
 
29
- function createTransformerFactory(
21
+ export const optimizer = (
30
22
  program: ts.Program,
31
23
  options?: Partial<OptimizerOptions>,
32
- ): ts.TransformerFactory<ts.SourceFile> {
24
+ ): ts.TransformerFactory<ts.SourceFile> => {
33
25
  const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
34
26
  const typeChecker = program.getTypeChecker();
35
27
  const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
36
- const constEnumRegistry = new ConstEnumRegistry(program);
37
- const enumEvaluator = new EnumEvaluator();
38
28
 
39
29
  const cache = new Map<ts.Symbol, VisibilityType>();
40
30
 
@@ -45,37 +35,6 @@ function createTransformerFactory(
45
35
 
46
36
  return (context: ts.TransformationContext) => {
47
37
  function transformNode(node: ts.Node): ts.Node | undefined {
48
- if (fullOptions.inlineConstEnums !== false) {
49
- if (ts.isPropertyAccessExpression(node)) {
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
- }
75
- }
76
- }
77
- }
78
-
79
38
  // const a = { node }
80
39
  if (ts.isShorthandPropertyAssignment(node)) {
81
40
  return handleShorthandPropertyAssignment(node);
@@ -578,7 +537,6 @@ function createTransformerFactory(
578
537
  return putToCache(nodeSymbol, VisibilityType.External);
579
538
  }
580
539
 
581
- // TODO: mute enum renames
582
540
  if (ts.isEnumDeclaration(currentNode)) {
583
541
  return putToCache(nodeSymbol, VisibilityType.External);
584
542
  }
@@ -658,84 +616,9 @@ function createTransformerFactory(
658
616
  return isSymbolClassMember(typeChecker.getSymbolAtLocation(node));
659
617
  }
660
618
 
661
- function tryInlineConstEnum(node: ts.PropertyAccessExpression): ts.Expression | null {
662
- const expressionType = typeChecker.getTypeAtLocation(node.expression);
663
- if (!isConstEnumType(expressionType)) return null;
664
-
665
- const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
666
- if (!enumSymbol) return null;
667
-
668
- const enumInfo = constEnumRegistry.getEnumInfo(enumSymbol);
669
- if (!enumInfo) return null;
670
-
671
- const memberValue = enumInfo.members.get(node.name.text)?.value;
672
- if (memberValue === undefined || memberValue === null) return null;
673
-
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;
700
- }
701
-
702
- return (sourceFile: ts.SourceFile) => {
703
- function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
704
- if (fullOptions.inlineConstEnums === false) return node;
705
- if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) return node;
706
-
707
- if (sourceFile.isDeclarationFile) {
708
- return ts.factory.updateEnumDeclaration(
709
- node,
710
- node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
711
- node.name,
712
- node.members,
713
- );
714
- }
715
-
716
- if (fullOptions.keepConstEnumEmptyDeclaration === true) {
717
- return ts.factory.updateEnumDeclaration(node, node.modifiers, node.name, []);
718
- }
719
- return undefined;
720
- }
721
-
722
- function wrapTransformNode(node: ts.Node): ts.Node | undefined {
723
- if (ts.isEnumDeclaration(node)) {
724
- const result = handleEnumDeclaration(node);
725
- if (result !== node) {
726
- if (result) {
727
- return transformNode(result);
728
- }
729
- return result;
730
- }
731
- }
732
- return transformNode(node);
733
- }
734
-
735
- const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
736
- ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
619
+ const transformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
620
+ ts.visitEachChild(transformNode(node), transformNodeAndChildren, context);
737
621
 
738
- return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
739
- };
622
+ return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile) as ts.SourceFile;
740
623
  };
741
- }
624
+ };
package/src/types.ts CHANGED
@@ -27,16 +27,6 @@ export interface OptimizerOptions {
27
27
  * A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
28
28
  */
29
29
  ignoreDecorated: boolean;
30
-
31
- /**
32
- * Whether to inline const enum values.
33
- */
34
- inlineConstEnums?: boolean;
35
-
36
- /**
37
- * Keep empty const enum declarations, keep imports / exports
38
- */
39
- keepConstEnumEmptyDeclaration?: boolean;
40
30
  }
41
31
 
42
32
  export const enum VisibilityType {
@@ -49,5 +39,4 @@ export const defaultOptions: OptimizerOptions = {
49
39
  privatePrefix: "$_",
50
40
  publicJSDocTag: "public",
51
41
  ignoreDecorated: false,
52
- inlineConstEnums: true,
53
42
  };
@@ -1,158 +0,0 @@
1
- import ts from "typescript";
2
-
3
- export type EnumValue = string | number;
4
-
5
- export interface EvaluationContext {
6
- localMembers: Map<string, EnumValue>;
7
- allowEnumReferences: boolean;
8
- }
9
-
10
- export class EnumEvaluator {
11
- private lastImplicitValue = -1;
12
-
13
- reset(): void {
14
- this.lastImplicitValue = -1;
15
- }
16
-
17
- evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
18
- if (ts.isPrefixUnaryExpression(expr)) {
19
- return this.evaluateUnary(expr, context);
20
- } else if (ts.isBinaryExpression(expr)) {
21
- return this.evaluateBinary(expr, context);
22
- } else if (ts.isStringLiteralLike(expr)) {
23
- return expr.text;
24
- } else if (ts.isNumericLiteral(expr)) {
25
- return +expr.text;
26
- } else if (ts.isParenthesizedExpression(expr)) {
27
- return this.evaluate(expr.expression, context);
28
- } else if (ts.isIdentifier(expr)) {
29
- return this.evaluateIdentifier(expr, context);
30
- } else if (
31
- expr.kind === ts.SyntaxKind.TrueKeyword ||
32
- expr.kind === ts.SyntaxKind.FalseKeyword ||
33
- expr.kind === ts.SyntaxKind.NullKeyword
34
- ) {
35
- throw this.createError(expr, `Unsupported literal: ${ts.SyntaxKind[expr.kind]}`);
36
- }
37
-
38
- throw this.createError(expr, `Cannot evaluate expression: ${expr.getText()}`);
39
- }
40
-
41
- evaluateEnumMember(member: ts.EnumMember, context: EvaluationContext): EnumValue {
42
- if (!member.initializer) {
43
- return this.evaluateImplicitMember(member);
44
- }
45
-
46
- const value = this.evaluate(member.initializer, context);
47
-
48
- if (typeof value === "number") {
49
- this.lastImplicitValue = value;
50
- }
51
-
52
- return value;
53
- }
54
-
55
- private evaluateImplicitMember(member: ts.EnumMember): EnumValue {
56
- const name = ts.isIdentifier(member.name) ? member.name.text : `<computed>`;
57
- void name;
58
-
59
- if (this.lastImplicitValue === -1) {
60
- this.lastImplicitValue = 0;
61
- return 0;
62
- }
63
-
64
- const nextValue = this.lastImplicitValue + 1;
65
- this.lastImplicitValue = nextValue;
66
- return nextValue;
67
- }
68
-
69
- createLiteral(value: EnumValue): ts.Expression {
70
- if (typeof value === "string") {
71
- return ts.factory.createStringLiteral(value);
72
- } else {
73
- if (value < 0) {
74
- return ts.factory.createPrefixMinus(ts.factory.createNumericLiteral(Math.abs(value).toString()));
75
- }
76
- return ts.factory.createNumericLiteral(value.toString());
77
- }
78
- }
79
-
80
- private evaluateUnary(expr: ts.PrefixUnaryExpression, context: EvaluationContext): number {
81
- const value = this.evaluate(expr.operand, context);
82
- if (typeof value !== "number") {
83
- throw this.createError(expr, `Unary operator requires numeric value, got ${typeof value}`);
84
- }
85
-
86
- switch (expr.operator) {
87
- case ts.SyntaxKind.PlusToken:
88
- return value;
89
- case ts.SyntaxKind.MinusToken:
90
- return -value;
91
- case ts.SyntaxKind.TildeToken:
92
- return ~value;
93
- default:
94
- throw this.createError(expr, `Unsupported unary operator: ${ts.SyntaxKind[expr.operator]}`);
95
- }
96
- }
97
-
98
- private evaluateBinary(expr: ts.BinaryExpression, context: EvaluationContext): EnumValue {
99
- const left = this.evaluate(expr.left, context);
100
- const right = this.evaluate(expr.right, context);
101
-
102
- if (typeof left === "string" && typeof right === "string" && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
103
- return left + right;
104
- }
105
-
106
- if (typeof left === "number" && typeof right === "number") {
107
- switch (expr.operatorToken.kind) {
108
- case ts.SyntaxKind.BarToken:
109
- return left | right;
110
- case ts.SyntaxKind.AmpersandToken:
111
- return left & right;
112
- case ts.SyntaxKind.GreaterThanGreaterThanToken:
113
- return left >> right;
114
- case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
115
- return left >>> right;
116
- case ts.SyntaxKind.LessThanLessThanToken:
117
- return left << right;
118
- case ts.SyntaxKind.CaretToken:
119
- return left ^ right;
120
- case ts.SyntaxKind.AsteriskToken:
121
- return left * right;
122
- case ts.SyntaxKind.SlashToken:
123
- return left / right;
124
- case ts.SyntaxKind.PlusToken:
125
- return left + right;
126
- case ts.SyntaxKind.MinusToken:
127
- return left - right;
128
- case ts.SyntaxKind.PercentToken:
129
- return left % right;
130
- case ts.SyntaxKind.AsteriskAsteriskToken:
131
- return left ** right;
132
- default:
133
- throw this.createError(expr, `Unsupported binary operator: ${ts.SyntaxKind[expr.operatorToken.kind]}`);
134
- }
135
- }
136
-
137
- throw this.createError(expr, `Cannot evaluate binary expression with types ${typeof left} and ${typeof right}`);
138
- }
139
-
140
- private evaluateIdentifier(expr: ts.Identifier, context: EvaluationContext): EnumValue {
141
- if (!context.allowEnumReferences) {
142
- throw this.createError(expr, `Cannot reference enum member here`);
143
- }
144
-
145
- const value = context.localMembers.get(expr.text);
146
- if (value === undefined) {
147
- throw this.createError(expr, `Undefined enum member: ${expr.text}`);
148
- }
149
-
150
- return value;
151
- }
152
-
153
- private createError(node: ts.Node, message: string): Error {
154
- const sourceFile = node.getSourceFile();
155
- const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
156
- return new Error(`${sourceFile.fileName}:${pos.line + 1}:${pos.character}: ${message}`);
157
- }
158
- }
@@ -1,152 +0,0 @@
1
- import ts from "typescript";
2
- import { EnumValue, EvaluationContext } from "./evaluator";
3
- import { EnumEvaluator } from "./evaluator";
4
- import { isConstEnumSymbol } from "./utils";
5
- import { hasModifier } from "../typescript-helpers";
6
-
7
- export interface ConstEnumInfo {
8
- declaration: ts.EnumDeclaration;
9
- members: Map<string, ConstEnumMemberInfo>;
10
- isExported: boolean;
11
- sourceFile: ts.SourceFile;
12
- }
13
-
14
- export interface ConstEnumMemberInfo {
15
- declaration: ts.EnumMember;
16
- name: string;
17
- value: EnumValue | null;
18
- }
19
-
20
- export class ConstEnumRegistry {
21
- private readonly program: ts.Program;
22
- private readonly typeChecker: ts.TypeChecker;
23
- private readonly enumDeclarations: Map<string, ConstEnumInfo>;
24
-
25
- constructor(program: ts.Program) {
26
- this.program = program;
27
- this.typeChecker = program.getTypeChecker();
28
- this.enumDeclarations = new Map();
29
- this.collectConstEnumsFromEntryPoints();
30
- }
31
-
32
- getEnum(enumName: string): ConstEnumInfo | undefined {
33
- return this.enumDeclarations.get(enumName);
34
- }
35
-
36
- getEnumInfo(symbol: ts.Symbol): ConstEnumInfo | undefined {
37
- const name = this.getEnumSymbolName(symbol);
38
- return this.enumDeclarations.get(name);
39
- }
40
-
41
- getMemberValue(enumName: string, memberName: string): EnumValue | undefined {
42
- const enumInfo = this.enumDeclarations.get(enumName);
43
- if (!enumInfo) return undefined;
44
- const memberInfo = enumInfo.members.get(memberName);
45
- return memberInfo?.value ?? undefined;
46
- }
47
-
48
- getAllEnums(): ConstEnumInfo[] {
49
- return Array.from(this.enumDeclarations.values());
50
- }
51
-
52
- getEnumCount(): number {
53
- return this.enumDeclarations.size;
54
- }
55
-
56
- private collectConstEnumsFromEntryPoints(): void {
57
- const sourceFiles = this.program.getSourceFiles();
58
-
59
- for (const sourceFile of sourceFiles) {
60
- if (sourceFile.isDeclarationFile) {
61
- continue;
62
- }
63
-
64
- this.registerConstEnumFromSource(sourceFile);
65
- }
66
- }
67
-
68
- private registerConstEnumFromSource(sourceFile: ts.SourceFile): void {
69
- ts.forEachChild(sourceFile, (node) => {
70
- if (ts.isEnumDeclaration(node) && hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
71
- const symbol = this.typeChecker.getSymbolAtLocation(node.name);
72
- if (symbol && isConstEnumSymbol(symbol)) {
73
- this.registerEnum(symbol, node, sourceFile);
74
- }
75
- }
76
- });
77
- }
78
-
79
- private registerEnum(symbol: ts.Symbol, declaration: ts.EnumDeclaration, sourceFile: ts.SourceFile): void {
80
- const name = this.getEnumSymbolName(symbol);
81
-
82
- if (this.enumDeclarations.has(name)) {
83
- return;
84
- }
85
-
86
- const isExported = this.hasExportModifier(declaration);
87
-
88
- const enumInfo: ConstEnumInfo = {
89
- declaration,
90
- members: new Map(),
91
- isExported,
92
- sourceFile,
93
- };
94
-
95
- this.evaluateEnumMembers(enumInfo);
96
-
97
- this.enumDeclarations.set(name, enumInfo);
98
- }
99
-
100
- private hasExportModifier(node: ts.Node): boolean {
101
- if (!ts.canHaveModifiers(node)) {
102
- return false;
103
- }
104
- const modifiers = ts.getModifiers(node);
105
- if (!modifiers) {
106
- return false;
107
- }
108
- return modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
109
- }
110
-
111
- private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
112
- const evaluator = new EnumEvaluator();
113
- evaluator.reset();
114
- const context: EvaluationContext = {
115
- localMembers: new Map(),
116
- allowEnumReferences: true,
117
- };
118
-
119
- for (const member of enumInfo.declaration.members) {
120
- if (!ts.isIdentifier(member.name)) continue;
121
-
122
- try {
123
- const value = evaluator.evaluateEnumMember(member, context);
124
- context.localMembers.set(member.name.text, value);
125
- enumInfo.members.set(member.name.text, {
126
- declaration: member,
127
- name: member.name.text,
128
- value,
129
- });
130
- } catch (error) {
131
- throw new Error(
132
- `Failed to evaluate const enum member ${enumInfo.declaration.name.text}.${member.name.text}: ${error}`,
133
- );
134
- }
135
- }
136
- }
137
-
138
- private getEnumDeclaration(symbol: ts.Symbol): ts.EnumDeclaration | null {
139
- const decl = symbol.declarations?.[0];
140
- if (decl && ts.isEnumDeclaration(decl)) {
141
- return decl;
142
- }
143
- return null;
144
- }
145
-
146
- private getEnumSymbolName(symbol: ts.Symbol): string {
147
- if (symbol.flags & ts.SymbolFlags.Alias) {
148
- return this.typeChecker.getAliasedSymbol(symbol).name;
149
- }
150
- return symbol.name;
151
- }
152
- }
@@ -1,13 +0,0 @@
1
- import ts from "typescript";
2
-
3
- export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
4
-
5
- export const isConstEnumType = (type: ts.Type | undefined): boolean => {
6
- if (type) {
7
- const symbol = type.symbol || type.aliasSymbol;
8
- if (symbol) {
9
- return (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
10
- }
11
- }
12
- return false;
13
- };
@@ -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;