ts-const-value-transformer 0.1.1 → 0.2.1

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.1
4
+
5
+ Fix for tracing imported symbol
6
+
7
+ ## v0.2.0
8
+
9
+ Add following options:
10
+
11
+ - `hoistEnumValues`
12
+ - `hoistExternalValues`
13
+ - `hoistPureFunctionCall`
14
+
3
15
  ## v0.1.1
4
16
 
5
17
  Add `createTransformer` export point
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,47 @@ 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
+ // We must follow 'aliased' symbol for parsing the symbol which name is not changed from the exported symbol name
150
+ const exportedSym = baseSym && baseSym.getFlags() & ts.SymbolFlags.Alias
151
+ ? typeChecker.getAliasedSymbol(baseSym)
152
+ : baseSym;
153
+ nodeFrom = exportedSym?.getDeclarations()?.[0];
154
+ }
155
+ const type = typeChecker.getTypeAtLocation(node);
156
+ const sym = type.getSymbol();
157
+ if (!sym) {
158
+ return false;
159
+ }
160
+ const def = sym.getDeclarations()?.[0];
161
+ if (!def) {
162
+ return false;
163
+ }
164
+ const typeDefinitionSource = def.getSourceFile();
165
+ if (program.isSourceFileFromExternalLibrary(typeDefinitionSource)) {
166
+ return true;
167
+ }
168
+ return false;
169
+ }
114
170
  function isAsConstExpression(node) {
115
171
  return node.type.getText() === 'const';
116
172
  }
@@ -145,6 +201,21 @@ function hasParentAsExpression(node, context, tsInstance) {
145
201
  }
146
202
  return hasParentAsExpression(node.parent, context, ts);
147
203
  }
204
+ function hasPureAnnotation(node, sourceFile, tsInstance) {
205
+ const ts = tsInstance;
206
+ const fullText = node.getFullText(sourceFile);
207
+ const ranges = ts.getLeadingCommentRanges(fullText, 0) ?? [];
208
+ for (const range of ranges) {
209
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
210
+ continue;
211
+ }
212
+ const text = fullText.slice(range.pos + 2, range.end - 2).trim();
213
+ if ((text[0] === '@' || text[0] === '#') && text.slice(1) === '__PURE__') {
214
+ return true;
215
+ }
216
+ }
217
+ return false;
218
+ }
148
219
  ////////////////////////////////////////////////////////////////////////////////
149
220
  export function printSource(sourceFile) {
150
221
  return printSourceImpl(sourceFile)[0];
@@ -1,2 +1,2 @@
1
- declare const _default: "0.1.1";
1
+ declare const _default: "0.2.1";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.1.1';
1
+ export default '0.2.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },