ts-const-value-transformer 0.1.0 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.0
4
+
5
+ Add following options:
6
+
7
+ - `hoistEnumValues`
8
+ - `hoistExternalValues`
9
+ - `hoistPureFunctionCall`
10
+
11
+ ## v0.1.1
12
+
13
+ Add `createTransformer` export point
14
+
3
15
  ## v0.1.0
4
16
 
5
17
  Initial version.
package/README.md CHANGED
@@ -150,14 +150,20 @@ export interface TransformOptions {
150
150
  ts?: typeof ts;
151
151
  /** Hoist property expressions (`x.prop`) which the value is constant. Default is true, but if the property getter has side effects (not recommended), set false explicitly. */
152
152
  hoistProperty?: boolean | undefined;
153
+ /** Hoist TypeScript's `enum` values (which are constant). Default is true, but if you want to preserve references, set false explicitly. Note that TypeScript compiler erases `const enum` references unless `preserveConstEnums` is true. */
154
+ hoistEnumValues?: boolean | undefined;
155
+ /** Hoist values defined in the external libraries. Default is true, but if the external libraries are not bundled, set false explicitly to keep references. */
156
+ hoistExternalValues?: boolean | undefined;
153
157
  /** Hoist function calls which the return value is constant. Default is false because function calls may have side effects. */
154
158
  unsafeHoistFunctionCall?: boolean | undefined;
159
+ /** Hoist function calls, with `@__PURE__` (or `#__PURE__`) comment annotation (must be a multi-line comment), which the return value is constant. Default is false, but if the function really has no side effects, you can safely specify true. If true, `unsafeHoistFunctionCall` option is ignored for `@__PURE__` functions */
160
+ hoistPureFunctionCall?: boolean | undefined;
155
161
  /** Hoist expressions with `as XXX`. Default is false because the base (non-`as`) value may be non-constant. */
156
162
  unsafeHoistAsExpresion?: boolean | undefined;
157
163
  }
158
164
  ```
159
165
 
160
- Note that you must pass `options` field to `createTransformer` function or `"plugins"` specifier.
166
+ Note that you must pass `options` field to `createTransformer` function or `"plugins"` specifier (see above examples).
161
167
 
162
168
  ### APIs
163
169
 
@@ -5,8 +5,14 @@ export interface TransformOptions {
5
5
  ts?: typeof ts;
6
6
  /** Hoist property expressions (`x.prop`) which the value is constant. Default is true, but if the property getter has side effects (not recommended), set false explicitly. */
7
7
  hoistProperty?: boolean | undefined;
8
+ /** Hoist TypeScript's `enum` values (which are constant). Default is true, but if you want to preserve references, set false explicitly. Note that TypeScript compiler erases `const enum` references unless `preserveConstEnums` is true. */
9
+ hoistEnumValues?: boolean | undefined;
10
+ /** Hoist values defined in the external libraries. Default is true, but if the external libraries are not bundled, set false explicitly to keep references. */
11
+ hoistExternalValues?: boolean | undefined;
8
12
  /** Hoist function calls which the return value is constant. Default is false because function calls may have side effects. */
9
13
  unsafeHoistFunctionCall?: boolean | undefined;
14
+ /** Hoist function calls, with `@__PURE__` (or `#__PURE__`) comment annotation (must be a multi-line comment), which the return value is constant. Default is false, but if the function really has no side effects, you can safely specify true. If true, `unsafeHoistFunctionCall` option is ignored for `@__PURE__` functions */
15
+ hoistPureFunctionCall?: boolean | undefined;
10
16
  /** Hoist expressions with `as XXX`. Default is false because the base (non-`as`) value may be non-constant. */
11
17
  unsafeHoistAsExpresion?: boolean | undefined;
12
18
  }
@@ -1,13 +1,16 @@
1
1
  import * as sourceMap from 'source-map';
2
2
  import * as ts from 'typescript';
3
3
  const SYMBOL_ORIGINAL_NODE = Symbol('originalNode');
4
- function assignDefaultValues(options) {
4
+ function assignDefaultValues(options = {}) {
5
5
  return {
6
- ts,
7
- hoistProperty: true,
8
- unsafeHoistAsExpresion: false,
9
- unsafeHoistFunctionCall: false,
10
- ...options,
6
+ // avoid using spread syntax to override `undefined` (not missing) values
7
+ ts: options.ts ?? ts,
8
+ hoistProperty: options.hoistProperty ?? true,
9
+ hoistEnumValues: options.hoistEnumValues ?? true,
10
+ hoistExternalValues: options.hoistExternalValues ?? true,
11
+ unsafeHoistAsExpresion: options.unsafeHoistAsExpresion ?? false,
12
+ hoistPureFunctionCall: options.hoistPureFunctionCall ?? false,
13
+ unsafeHoistFunctionCall: options.unsafeHoistFunctionCall ?? false,
11
14
  };
12
15
  }
13
16
  ////////////////////////////////////////////////////////////////////////////////
@@ -37,12 +40,24 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
37
40
  (!options.hoistProperty &&
38
41
  ts.isPropertyAccessExpression(node) &&
39
42
  !isEnumAccess(node, program, ts)) ||
40
- (!options.unsafeHoistFunctionCall && ts.isCallLikeExpression(node)) ||
43
+ (!options.hoistEnumValues &&
44
+ ((ts.isPropertyAccessExpression(node) &&
45
+ isEnumAccess(node, program, ts)) ||
46
+ (ts.isIdentifier(node) && isEnumIdentifier(node, program, ts)))) ||
41
47
  node.kind === ts.SyntaxKind.TrueKeyword ||
42
48
  node.kind === ts.SyntaxKind.FalseKeyword ||
43
49
  node.kind === ts.SyntaxKind.NullKeyword) {
44
50
  return node;
45
51
  }
52
+ if (!options.unsafeHoistFunctionCall &&
53
+ (!options.hoistPureFunctionCall || !hasPureAnnotation(node, sourceFile, ts))) {
54
+ if (ts.isCallLikeExpression(node)) {
55
+ return node;
56
+ }
57
+ }
58
+ if (!options.hoistExternalValues && isExternalReference(node, program)) {
59
+ return node;
60
+ }
46
61
  if (ts.isIdentifier(node)) {
47
62
  if (
48
63
  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
@@ -111,6 +126,43 @@ function isEnumAccess(node, program, tsInstance) {
111
126
  const type = typeChecker.getTypeAtLocation(node);
112
127
  return (type.getFlags() & ts.TypeFlags.EnumLiteral) !== 0;
113
128
  }
129
+ function isEnumIdentifier(node, program, tsInstance) {
130
+ const ts = tsInstance;
131
+ const typeChecker = program.getTypeChecker();
132
+ const type = typeChecker.getTypeAtLocation(node);
133
+ return (type.getFlags() & ts.TypeFlags.EnumLiteral) !== 0;
134
+ }
135
+ function isExternalReference(node, program) {
136
+ const typeChecker = program.getTypeChecker();
137
+ const nodeSym = typeChecker.getSymbolAtLocation(node);
138
+ let nodeFrom = nodeSym?.getDeclarations()?.[0];
139
+ while (nodeFrom) {
140
+ if (program.isSourceFileFromExternalLibrary(nodeFrom.getSourceFile())) {
141
+ return true;
142
+ }
143
+ // Walk into the 'import' variables
144
+ if (!ts.isImportSpecifier(nodeFrom)) {
145
+ break;
146
+ }
147
+ const baseName = nodeFrom.propertyName ?? nodeFrom.name;
148
+ const baseSym = typeChecker.getSymbolAtLocation(baseName);
149
+ nodeFrom = baseSym?.getDeclarations()?.[0];
150
+ }
151
+ const type = typeChecker.getTypeAtLocation(node);
152
+ const sym = type.getSymbol();
153
+ if (!sym) {
154
+ return false;
155
+ }
156
+ const def = sym.getDeclarations()?.[0];
157
+ if (!def) {
158
+ return false;
159
+ }
160
+ const typeDefinitionSource = def.getSourceFile();
161
+ if (program.isSourceFileFromExternalLibrary(typeDefinitionSource)) {
162
+ return true;
163
+ }
164
+ return false;
165
+ }
114
166
  function isAsConstExpression(node) {
115
167
  return node.type.getText() === 'const';
116
168
  }
@@ -145,6 +197,21 @@ function hasParentAsExpression(node, context, tsInstance) {
145
197
  }
146
198
  return hasParentAsExpression(node.parent, context, ts);
147
199
  }
200
+ function hasPureAnnotation(node, sourceFile, tsInstance) {
201
+ const ts = tsInstance;
202
+ const fullText = node.getFullText(sourceFile);
203
+ const ranges = ts.getLeadingCommentRanges(fullText, 0) ?? [];
204
+ for (const range of ranges) {
205
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
206
+ continue;
207
+ }
208
+ const text = fullText.slice(range.pos + 2, range.end - 2).trim();
209
+ if ((text[0] === '@' || text[0] === '#') && text.slice(1) === '__PURE__') {
210
+ return true;
211
+ }
212
+ }
213
+ return false;
214
+ }
148
215
  ////////////////////////////////////////////////////////////////////////////////
149
216
  export function printSource(sourceFile) {
150
217
  return printSourceImpl(sourceFile)[0];
@@ -1,2 +1,2 @@
1
- declare const _default: "0.1.0";
1
+ declare const _default: "0.2.0";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.1.0';
1
+ export default '0.2.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },
@@ -13,6 +13,16 @@
13
13
  "import": "./dist/index.mjs",
14
14
  "types": "./dist/index.d.mts"
15
15
  },
16
+ "./createTransformer": {
17
+ "default": "./dist/createTransformer.mjs",
18
+ "import": "./dist/createTransformer.mjs",
19
+ "types": "./dist/createTransformer.d.mts"
20
+ },
21
+ "./createTransformer.mjs": {
22
+ "default": "./dist/createTransformer.mjs",
23
+ "import": "./dist/createTransformer.mjs",
24
+ "types": "./dist/createTransformer.d.mts"
25
+ },
16
26
  "./getCustomTransformers": {
17
27
  "default": "./dist/getCustomTransformers.mjs",
18
28
  "import": "./dist/getCustomTransformers.mjs",