ts-const-value-transformer 0.6.0 → 0.7.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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.0
4
+
5
+ - Fix for printing 'minus numeric value' and 'void 0', and remove using `ts.createPrinter`
6
+ - Fix that `recreateProgram` creates the new fresh program instead of passing `oldProgram`
7
+ - Fix to cache source code with no transformation
8
+ - Add option to disable caching original source content (changed to false by default)
9
+ - Fix some codes for better memory usage
10
+
3
11
  ## v0.6.0
4
12
 
5
13
  - Remove skipping satisfies expression
package/README.md CHANGED
@@ -295,6 +295,8 @@ interface CreatePortalTransformerOptions extends TransformOptions {
295
295
  * If 0 or `undefined`, recreation will not be performed.
296
296
  */
297
297
  recreateProgramOnTransformCount?: number;
298
+ /** Specifies to cache base (original) source code for check if the input is changed. Default is false. */
299
+ cacheBaseSource?: boolean;
298
300
  }
299
301
  ```
300
302
 
@@ -9,12 +9,22 @@ export interface CreatePortalTransformerOptions extends TransformOptions {
9
9
  /** The current directory for file search. Also affects to `project` option. */
10
10
  cwd?: string;
11
11
  /**
12
- * Speficies the count. When the transformation count reaches this value, `program` instance will be recreated (and count will be reset).
12
+ * Specifies the count. When the transformation count reaches this value, `program` instance will be recreated (and count will be reset).
13
13
  * This is useful if the project is big and out-of-memory occurs during transformation, but the process may be slower.
14
14
  * If 0 or `undefined`, recreation will not be performed.
15
15
  */
16
16
  recreateProgramOnTransformCount?: number;
17
+ /** Specifies to cache base (original) source code for check if the input is changed. Default is false. */
18
+ cacheBaseSource?: boolean;
17
19
  }
20
+ export type PortalTransformerResult = [
21
+ newSource: string | null,
22
+ newSourceMap: RawSourceMap | undefined
23
+ ];
24
+ export type PortalTransformerResultNonNull = [
25
+ newSource: string,
26
+ newSourceMap: RawSourceMap | undefined
27
+ ];
18
28
  export interface PortalTransformer {
19
29
  /** The `typescript` namespace object */
20
30
  readonly ts: typeof tsNamespace;
@@ -32,7 +42,7 @@ export interface PortalTransformer {
32
42
  * @param options Transform options (addition to `options` passed to `createPortalTransformer`)
33
43
  * @returns Tuple of new source code and source map. Source map may be undefined if source code is unchanged.
34
44
  */
35
- transform(content: string, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): [newSource: string, newSourceMap: RawSourceMap | undefined];
45
+ transform(content: string, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): PortalTransformerResultNonNull;
36
46
  /**
37
47
  * Performs transformation.
38
48
  * @param content Base source code. If null, uses loaded source code in the TS project.
@@ -41,7 +51,7 @@ export interface PortalTransformer {
41
51
  * @param options Transform options (addition to `options` passed to `createPortalTransformer`)
42
52
  * @returns Tuple of new source code and source map. Source map may be undefined if source code is unchanged.
43
53
  */
44
- transform(content: string | null, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): [newSource: string | null, newSourceMap: RawSourceMap | undefined];
54
+ transform(content: string | null, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): PortalTransformerResult;
45
55
  }
46
56
  /**
47
57
  * Creates the new portal transformer instance for the TS project.
@@ -20,6 +20,7 @@ function createPortalTransformerImpl(options, ts) {
20
20
  const ignoreFiles = getIgnoreFilesFunction(options.ignoreFiles);
21
21
  const cwd = options.cwd ?? process.cwd();
22
22
  const recreateProgramOnTransformCount = options.recreateProgramOnTransformCount ?? 0;
23
+ const cacheBaseSource = options.cacheBaseSource ?? false;
23
24
  // eslint-disable-next-line @typescript-eslint/unbound-method
24
25
  const foundConfigPath = ts.findConfigFile(cwd, ts.sys.fileExists, project);
25
26
  if (foundConfigPath == null) {
@@ -50,11 +51,15 @@ function createPortalTransformerImpl(options, ts) {
50
51
  });
51
52
  let transformationCount = 0;
52
53
  const recreateProgram = () => {
53
- const oldProgram = program;
54
+ // @ts-expect-error: We must clear reference first to give change for gc
55
+ delete instance.program;
56
+ // @ts-expect-error: We must clear reference first to give change for gc
57
+ program = null;
58
+ // We don't pass `oldProgram` because the transformed source codes should not be necessary (the transformation does not change logics and types)
59
+ // If we pass `oldProgram`, temporal memory usage may increase because gc cannot release `oldProgram` before creating new program
54
60
  program = ts.createProgram({
55
61
  options: config.options,
56
62
  rootNames: config.fileNames,
57
- oldProgram,
58
63
  });
59
64
  instance.program = program;
60
65
  transformationCount = 0;
@@ -69,7 +74,7 @@ function createPortalTransformerImpl(options, ts) {
69
74
  const individualOptionsJson = optionsToString(individualOptions ?? {});
70
75
  const cachedData = cache.get(fileName);
71
76
  if (cachedData &&
72
- cachedData.content === content &&
77
+ (!cacheBaseSource || cachedData.content === content) &&
73
78
  cachedData.optJson === individualOptionsJson) {
74
79
  return cachedData.result;
75
80
  }
@@ -80,15 +85,15 @@ function createPortalTransformerImpl(options, ts) {
80
85
  if (ignoreFiles(fileName)) {
81
86
  return [content, rawSourceMap];
82
87
  }
88
+ const sourceFile = program.getSourceFile(fileName);
89
+ if (!sourceFile) {
90
+ return [content, rawSourceMap];
91
+ }
83
92
  transformationCount++;
84
93
  if (recreateProgramOnTransformCount > 0 &&
85
94
  transformationCount >= recreateProgramOnTransformCount) {
86
95
  recreateProgram();
87
96
  }
88
- const sourceFile = program.getSourceFile(fileName);
89
- if (!sourceFile) {
90
- return [content, rawSourceMap];
91
- }
92
97
  // If input content is changed, replace it
93
98
  if (content != null && sourceFile.text !== content) {
94
99
  sourceFile.update(content, {
@@ -102,12 +107,19 @@ function createPortalTransformerImpl(options, ts) {
102
107
  });
103
108
  const transformResult = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
104
109
  const transformedSource = transformResult.transformed[0];
110
+ let result;
105
111
  // If unchanged, return base file as-is
106
112
  if (transformedSource === sourceFile) {
107
- return [content ?? sourceFile.text, rawSourceMap];
113
+ result = [content ?? sourceFile.text, rawSourceMap];
108
114
  }
109
- const result = printSourceWithMap(transformedSource, fileName, rawSourceMap, ts);
110
- cache.set(fileName, { content, optJson: individualOptionsJson, result });
115
+ else {
116
+ result = printSourceWithMap(transformedSource, fileName, rawSourceMap, ts);
117
+ }
118
+ cache.set(fileName, {
119
+ content: cacheBaseSource ? content : '',
120
+ optJson: individualOptionsJson,
121
+ result,
122
+ });
111
123
  return result;
112
124
  },
113
125
  };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import createPortalTransformer, { createPortalTransformerSync, type CreatePortalTransformerOptions, type PortalTransformer } from './createPortalTransformer.mjs';
1
+ import createPortalTransformer, { createPortalTransformerSync, type CreatePortalTransformerOptions, type PortalTransformer, type PortalTransformerResult, type PortalTransformerResultNonNull } from './createPortalTransformer.mjs';
2
2
  import createTransformer from './createTransformer.mjs';
3
3
  import version from './version.mjs';
4
4
  export { printSource, printSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
5
- export { createPortalTransformer, createPortalTransformerSync, createTransformer, type CreatePortalTransformerOptions, type PortalTransformer, version, };
5
+ export { createPortalTransformer, createPortalTransformerSync, createTransformer, type CreatePortalTransformerOptions, type PortalTransformer, type PortalTransformerResult, type PortalTransformerResultNonNull, version, };
@@ -1,6 +1,6 @@
1
1
  import * as sourceMap from 'source-map';
2
2
  import * as tsNamespace from 'typescript';
3
- const SYMBOL_ORIGINAL_NODE = Symbol('originalNode');
3
+ const SYMBOL_ORIGINAL_NODE_DATA = Symbol('originalNodeData');
4
4
  function assignDefaultValues(options = {}) {
5
5
  return {
6
6
  // avoid using spread syntax to override `undefined` (not missing) values
@@ -46,7 +46,7 @@ export function transformSource(sourceFile, program, context, options) {
46
46
  function visitNodeChildren(node, parent, sourceFile, program, options) {
47
47
  const ts = options.ts;
48
48
  const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options);
49
- if (newNode[SYMBOL_ORIGINAL_NODE]) {
49
+ if (newNode[SYMBOL_ORIGINAL_NODE_DATA]) {
50
50
  return newNode;
51
51
  }
52
52
  // skip statements which would not have 'value' expressions
@@ -126,34 +126,50 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options)
126
126
  if (type.isUnionOrIntersection()) {
127
127
  return node;
128
128
  }
129
+ let newSource;
129
130
  if (type.isStringLiteral()) {
130
131
  newNode = ts.factory.createStringLiteral(type.value);
132
+ newSource =
133
+ // TypeScript namespace may export `function escapeNonAsciiString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string`
134
+ 'escapeNonAsciiString' in ts
135
+ ? `"${ts.escapeNonAsciiString(type.value, 'CharacterCodes' in ts
136
+ ? ts.CharacterCodes.doubleQuote
137
+ : 34 /* doubleQuote */)}"`
138
+ : JSON.stringify(type.value);
131
139
  }
132
140
  else if (type.isNumberLiteral()) {
133
141
  if (type.value < 0) {
134
- newNode = ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value));
142
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value)));
143
+ newSource = `(-${-type.value})`;
135
144
  }
136
145
  else {
137
146
  newNode = ts.factory.createNumericLiteral(type.value);
147
+ newSource = `${type.value}`;
138
148
  }
139
149
  }
140
150
  else if (flags & ts.TypeFlags.BigIntLiteral) {
141
- newNode = ts.factory.createBigIntLiteral(typeChecker.typeToString(type));
151
+ const text = typeChecker.typeToString(type);
152
+ newNode = ts.factory.createBigIntLiteral(text);
153
+ newSource = text;
142
154
  }
143
155
  else if (flags & ts.TypeFlags.BooleanLiteral) {
144
156
  const text = typeChecker.typeToString(type);
145
157
  newNode =
146
158
  text === 'true' ? ts.factory.createTrue() : ts.factory.createFalse();
159
+ newSource = text;
147
160
  }
148
161
  else if (flags & ts.TypeFlags.Null) {
149
162
  newNode = ts.factory.createNull();
163
+ newSource = 'null';
150
164
  }
151
165
  else if (flags & ts.TypeFlags.Undefined) {
152
166
  if (options.useUndefinedSymbolForUndefinedValue) {
153
167
  newNode = ts.factory.createIdentifier('undefined');
168
+ newSource = 'undefined';
154
169
  }
155
170
  else {
156
- newNode = ts.factory.createVoidZero();
171
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createVoidZero());
172
+ newSource = '(void 0)';
157
173
  }
158
174
  }
159
175
  else {
@@ -161,7 +177,12 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options)
161
177
  }
162
178
  const result = ts.addSyntheticTrailingComment(newNode, ts.SyntaxKind.MultiLineCommentTrivia, ` ${node.getText(sourceFile)} `);
163
179
  ts.setTextRange(result, node);
164
- result[SYMBOL_ORIGINAL_NODE] = node;
180
+ result[SYMBOL_ORIGINAL_NODE_DATA] = [
181
+ node.getText(sourceFile),
182
+ newSource,
183
+ node.pos,
184
+ node.end,
185
+ ];
165
186
  return result;
166
187
  }
167
188
  catch {
@@ -432,22 +453,27 @@ function positionToLineAndColumn(sourceFile, pos, generatedDiff) {
432
453
  }
433
454
  function printSourceImpl(tsInstance, sourceFile, originalSourceName, mapGenerator) {
434
455
  const ts = tsInstance ?? tsNamespace;
435
- const printer = ts.createPrinter({ removeComments: true });
436
- const r = printNode(ts, printer, sourceFile.getFullText(), sourceFile, sourceFile, { pos: 0, diff: 0, lastLine: 0 }, originalSourceName, mapGenerator);
437
- return [r, mapGenerator?.toJSON()];
456
+ const r = printNode(ts, sourceFile.getFullText(), sourceFile, sourceFile, { pos: 0, diff: 0, lastLine: 0 }, originalSourceName, mapGenerator);
457
+ // This forces to concatenate strings into flatten one to reduce object trees for ConsString
458
+ void (r | 0);
459
+ const json = mapGenerator?.toJSON();
460
+ if (json) {
461
+ void (json.mappings | 0);
462
+ }
463
+ return [r, json];
438
464
  }
439
- function printNode(tsInstance, printer, baseSource, sourceFile, node, posContext, originalSourceName, mapGenerator) {
440
- const originalNode = node[SYMBOL_ORIGINAL_NODE];
441
- if (originalNode) {
442
- let result = printer.printNode(tsInstance.EmitHint.Unspecified, node, sourceFile);
465
+ function printNode(tsInstance, baseSource, sourceFile, node, posContext, originalSourceName, mapGenerator) {
466
+ const originalNodeData = node[SYMBOL_ORIGINAL_NODE_DATA];
467
+ if (originalNodeData) {
468
+ let result = originalNodeData[1];
443
469
  const comments = tsInstance.getSyntheticTrailingComments(node);
444
470
  if (comments) {
445
471
  for (const comment of comments) {
446
472
  result += ` /*${comment.text}*/`;
447
473
  }
448
474
  }
449
- const old = originalNode.getText(sourceFile);
450
- const oldFull = baseSource.substring(originalNode.pos, originalNode.end);
475
+ const old = originalNodeData[0];
476
+ const oldFull = baseSource.substring(originalNodeData[2], originalNodeData[3]);
451
477
  const i = oldFull.lastIndexOf(old);
452
478
  const leadingUnchanged = i < 0 ? 0 : i;
453
479
  const newText = i < 0
@@ -490,7 +516,7 @@ function printNode(tsInstance, printer, baseSource, sourceFile, node, posContext
490
516
  addMappingForCurrent();
491
517
  posContext.pos = child.pos;
492
518
  }
493
- output += printNode(tsInstance, printer, baseSource, sourceFile, child, posContext, originalSourceName, mapGenerator);
519
+ output += printNode(tsInstance, baseSource, sourceFile, child, posContext, originalSourceName, mapGenerator);
494
520
  lastChildPos = child.end;
495
521
  return child;
496
522
  }, void 0);
@@ -1,2 +1,2 @@
1
- declare const _default: "0.6.0";
1
+ declare const _default: "0.7.0";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.6.0';
1
+ export default '0.7.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },