ts-const-value-transformer 0.8.2 → 0.9.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.9.1
4
+
5
+ - Add `hoistConstTemplateLiteral` option
6
+ - Fix for the return type of `getCustomTransformers`
7
+
8
+ ## v0.9.0
9
+
10
+ - Add `transformAndPrintSource` and `transformAndPrintSourceWithMap` to visit nodes only once, and use them in portal transformer
11
+
3
12
  ## v0.8.2
4
13
 
5
14
  - Fix to use internal functions to reduce memory usage
package/README.md CHANGED
@@ -166,6 +166,8 @@ export interface TransformOptions {
166
166
  useUndefinedSymbolForUndefinedValue?: boolean | undefined;
167
167
  /** Hoist `undefined` symbol to `void 0` (or `undefined` if useUndefinedSymbolForUndefinedValue is true). Default is true. */
168
168
  hoistUndefinedSymbol?: boolean | undefined;
169
+ /** Hoist template literals with constant (not including variables). Default is false because it is not necessary for ES2015 or later; Script minifiers would optimize for those values. For ES5 (which will be deprecated in the future), TypeScript converts template literals to such as `''.concat(...)`, which is not treated as constant values. */
170
+ hoistConstTemplateLiteral?: boolean | undefined;
169
171
  /**
170
172
  * External names (tested with `.includes()` for string, with `.test()` for RegExp) for `hoistExternalValues` settings (If `hoistExternalValues` is not specified, this setting will be used).
171
173
  * - Path separators for input file name are always normalized to '/' internally.
@@ -260,6 +262,17 @@ Prints (generates) source code from `SourceFile`, along with raw source-map data
260
262
  - `originalSourceName` would be the file name of `sourceFile`, but you can specify another name.
261
263
  - `startOfSourceMap` is a base source map (if original source file is a generated-content) if available.
262
264
 
265
+ #### transformAndPrintSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions) => string
266
+
267
+ Transforms the source file with TypeScript project and prints a new source code. This acts like combination of `transformSource` and `printSource`, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
268
+
269
+ #### transformAndPrintSourceWithMap: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, originalSourceName: string, options?: TransformOptions, startOfSourceMap?: RawSourceMap) => [string, RawSourceMap]
270
+
271
+ Transforms the source file with TypeScript project and prints a new source code. This acts like combination of `transformSource` and `printSourceWithMap`, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
272
+
273
+ - `originalSourceName` would be the file name of `sourceFile`, but you can specify another name.
274
+ - `startOfSourceMap` is a base source map (if original source file is a generated-content) if available.
275
+
263
276
  #### transformSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions) => ts.SourceFile
264
277
 
265
278
  Transforms the source file with TypeScript project. You don't need to call this function directly; use `createTransformer` or `createPortalTransformer` instead.
@@ -1,8 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import { createRequire } from 'module';
3
3
  import * as path from 'path';
4
- import createTransformer from './createTransformer.mjs';
5
- import { getIgnoreFilesFunction, printSourceWithMap, } from './transform.mjs';
4
+ import { getIgnoreFilesFunction, transformAndPrintSourceWithMap, } from './transform.mjs';
6
5
  const require = createRequire(import.meta.url);
7
6
  function optionsToString(options) {
8
7
  return JSON.stringify(options, (key, value) => {
@@ -105,18 +104,9 @@ function createPortalTransformerImpl(options, ts) {
105
104
  });
106
105
  sourceFile.text = content;
107
106
  }
108
- const transformer = createTransformer(program, {
109
- options: { ...options, ...individualOptions, ts },
110
- });
111
- const transformResult = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
112
- const transformedSource = transformResult.transformed[0];
113
- let result;
114
- // If unchanged, return base file as-is
115
- if (transformedSource === sourceFile) {
116
- result = [content ?? sourceFile.text, rawSourceMap];
117
- }
118
- else {
119
- result = printSourceWithMap(transformedSource, fileName, rawSourceMap, ts);
107
+ const result = transformAndPrintSourceWithMap(sourceFile, program, undefined, fileName, { ...options, ...individualOptions, ts }, rawSourceMap);
108
+ if (sourceFile.text === result[0]) {
109
+ result[1] = undefined;
120
110
  }
121
111
  if (cacheResult) {
122
112
  // This forces to concatenate strings into flatten one, to reduce object trees for ConsString
@@ -1,6 +1,2 @@
1
1
  import type * as ts from 'typescript';
2
- export default function getCustomTransformers(program: ts.Program, _getProgram: () => ts.Program): {
3
- before?: Array<ts.TransformerFactory<ts.SourceFile>>;
4
- after?: Array<ts.TransformerFactory<ts.SourceFile>>;
5
- afterDeclarations?: Array<ts.TransformerFactory<ts.SourceFile>>;
6
- };
2
+ export default function getCustomTransformers(program: ts.Program, _getProgram: () => ts.Program): ts.CustomTransformers;
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
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
- export { printSource, printSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
4
+ export { printSource, printSourceWithMap, transformAndPrintSource, transformAndPrintSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
5
5
  export { createPortalTransformer, createPortalTransformerSync, createTransformer, type CreatePortalTransformerOptions, type PortalTransformer, type PortalTransformerResult, type PortalTransformerResultNonNull, version, };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import createPortalTransformer, { createPortalTransformerSync, } from './createPortalTransformer.mjs';
2
2
  import createTransformer from './createTransformer.mjs';
3
3
  import version from './version.mjs';
4
- export { printSource, printSourceWithMap, transformSource, } from './transform.mjs';
4
+ export { printSource, printSourceWithMap, transformAndPrintSource, transformAndPrintSourceWithMap, transformSource, } from './transform.mjs';
5
5
  export { createPortalTransformer, createPortalTransformerSync, createTransformer, version, };
@@ -21,6 +21,8 @@ export interface TransformOptions {
21
21
  useUndefinedSymbolForUndefinedValue?: boolean | undefined;
22
22
  /** Hoist `undefined` symbol to `void 0` (or `undefined` if {@linkcode useUndefinedSymbolForUndefinedValue} is true). Default is true. */
23
23
  hoistUndefinedSymbol?: boolean | undefined;
24
+ /** Hoist template literals with constant (not including variables). Default is false because it is not necessary for ES2015 or later; Script minifiers would optimize for those values. For ES5 (which will be deprecated in the future), TypeScript converts template literals to such as `''.concat(...)`, which is not treated as constant values. */
25
+ hoistConstTemplateLiteral?: boolean | undefined;
24
26
  /**
25
27
  * External names (tested with `.includes()` for string, with `.test()` for RegExp) for `hoistExternalValues` settings (If `hoistExternalValues` is not specified, this setting will be used).
26
28
  * - Path separators for input file name are always normalized to '/' internally.
@@ -39,3 +41,5 @@ export declare function getIgnoreFilesFunction(ignoreFiles: TransformOptions['ig
39
41
  export declare function transformSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions): ts.SourceFile;
40
42
  export declare function printSource(sourceFile: ts.SourceFile, tsInstance?: typeof ts): string;
41
43
  export declare function printSourceWithMap(sourceFile: ts.SourceFile, originalSourceName: string, startOfSourceMap?: sourceMap.RawSourceMap, tsInstance?: typeof ts): [string, sourceMap.RawSourceMap];
44
+ export declare function transformAndPrintSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions): string;
45
+ export declare function transformAndPrintSourceWithMap(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, originalSourceName: string, options?: TransformOptions, startOfSourceMap?: sourceMap.RawSourceMap): [string, sourceMap.RawSourceMap];
@@ -14,6 +14,7 @@ function assignDefaultValues(options = {}) {
14
14
  unsafeHoistWritableValues: options.unsafeHoistWritableValues ?? false,
15
15
  useUndefinedSymbolForUndefinedValue: options.useUndefinedSymbolForUndefinedValue ?? false,
16
16
  hoistUndefinedSymbol: options.hoistUndefinedSymbol ?? true,
17
+ hoistConstTemplateLiteral: options.hoistConstTemplateLiteral ?? false,
17
18
  externalNames: options.externalNames ?? [],
18
19
  ignoreFiles: options.ignoreFiles ?? [],
19
20
  };
@@ -41,25 +42,44 @@ export function getIgnoreFilesFunction(ignoreFiles) {
41
42
  ////////////////////////////////////////////////////////////////////////////////
42
43
  export function transformSource(sourceFile, program, context, options) {
43
44
  const requiredOptions = assignDefaultValues(options);
44
- return requiredOptions.ts.visitEachChild(sourceFile, (node) => visitNodeChildren(node, sourceFile, sourceFile, program, requiredOptions, context), context);
45
+ const ts = requiredOptions.ts;
46
+ return ts.visitEachChild(sourceFile, (node) => visitNodeChildren(node, sourceFile, sourceFile, program, requiredOptions, context, (node) => {
47
+ // skip statements which would not have 'value' expressions
48
+ if (ts.isInterfaceDeclaration(node) ||
49
+ ts.isTypeAliasDeclaration(node) ||
50
+ // Identifies in import clause should not be parsed
51
+ ts.isImportDeclaration(node) ||
52
+ ts.isTypeOnlyExportDeclaration(node)) {
53
+ return false;
54
+ }
55
+ return true;
56
+ }), context);
45
57
  }
46
- function visitNodeChildren(node, parent, sourceFile, program, options, context) {
58
+ function visitNodeChildren(node, parent, sourceFile, program, options, context, fnVisit, visitContext, fnVisitBeforeReplace, fnVisitBeforeChild, fnVisitAfterChild, fnReplace) {
47
59
  const ts = options.ts;
48
- const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context);
49
- if (newNode[SYMBOL_ORIGINAL_NODE_DATA]) {
50
- return newNode;
60
+ if (fnVisitBeforeReplace) {
61
+ fnVisitBeforeReplace(node, parent, visitContext);
62
+ }
63
+ const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context, visitContext, fnReplace ?? 'create');
64
+ if (newNode == null) {
65
+ return node;
51
66
  }
52
- // skip statements which would not have 'value' expressions
53
- if (ts.isInterfaceDeclaration(newNode) ||
54
- ts.isTypeAliasDeclaration(newNode) ||
55
- // Identifies in import clause should not be parsed
56
- ts.isImportDeclaration(newNode) ||
57
- ts.isTypeOnlyExportDeclaration(newNode)) {
67
+ if (newNode !== node) {
58
68
  return newNode;
59
69
  }
60
- return ts.visitEachChild(newNode, (node) => visitNodeChildren(node, newNode, sourceFile, program, options, context), context);
70
+ if (!fnVisit(node, parent, visitContext)) {
71
+ return node;
72
+ }
73
+ const childVisitContext = fnVisitBeforeChild
74
+ ? fnVisitBeforeChild(node, parent, visitContext)
75
+ : visitContext;
76
+ const r = ts.visitEachChild(node, (nodeChild) => visitNodeChildren(nodeChild, node, sourceFile, program, options, context, fnVisit, childVisitContext, fnVisitBeforeReplace, fnVisitBeforeChild, fnVisitAfterChild, fnReplace), context);
77
+ if (fnVisitAfterChild) {
78
+ fnVisitAfterChild(node, parent, childVisitContext);
79
+ }
80
+ return r;
61
81
  }
62
- function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context) {
82
+ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context, visitContext, replaceOption) {
63
83
  const ts = options.ts;
64
84
  if (ts.isCallLikeExpression(node)) {
65
85
  if (!ts.isExpression(node) ||
@@ -101,6 +121,11 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
101
121
  return node;
102
122
  }
103
123
  }
124
+ else if (ts.isTemplateExpression(node)) {
125
+ if (!options.hoistConstTemplateLiteral) {
126
+ return node;
127
+ }
128
+ }
104
129
  else {
105
130
  return node;
106
131
  }
@@ -127,7 +152,9 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
127
152
  }
128
153
  let newSource;
129
154
  if (type.isStringLiteral()) {
130
- newNode = ts.factory.createStringLiteral(type.value);
155
+ if (replaceOption === 'create') {
156
+ newNode = ts.factory.createStringLiteral(type.value);
157
+ }
131
158
  newSource =
132
159
  // TypeScript namespace may export `function escapeNonAsciiString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string`
133
160
  'escapeNonAsciiString' in ts
@@ -138,36 +165,50 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
138
165
  }
139
166
  else if (type.isNumberLiteral()) {
140
167
  if (type.value < 0) {
141
- newNode = ts.factory.createParenthesizedExpression(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value)));
168
+ if (replaceOption === 'create') {
169
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value)));
170
+ }
142
171
  newSource = `(-${-type.value})`;
143
172
  }
144
173
  else {
145
- newNode = ts.factory.createNumericLiteral(type.value);
174
+ if (replaceOption === 'create') {
175
+ newNode = ts.factory.createNumericLiteral(type.value);
176
+ }
146
177
  newSource = `${type.value}`;
147
178
  }
148
179
  }
149
180
  else if (flags & ts.TypeFlags.BigIntLiteral) {
150
181
  const text = typeChecker.typeToString(type);
151
- newNode = ts.factory.createBigIntLiteral(text);
182
+ if (replaceOption === 'create') {
183
+ newNode = ts.factory.createBigIntLiteral(text);
184
+ }
152
185
  newSource = text;
153
186
  }
154
187
  else if (flags & ts.TypeFlags.BooleanLiteral) {
155
188
  const text = typeChecker.typeToString(type);
156
- newNode =
157
- text === 'true' ? ts.factory.createTrue() : ts.factory.createFalse();
189
+ if (replaceOption === 'create') {
190
+ newNode =
191
+ text === 'true' ? ts.factory.createTrue() : ts.factory.createFalse();
192
+ }
158
193
  newSource = text;
159
194
  }
160
195
  else if (flags & ts.TypeFlags.Null) {
161
- newNode = ts.factory.createNull();
196
+ if (replaceOption === 'create') {
197
+ newNode = ts.factory.createNull();
198
+ }
162
199
  newSource = 'null';
163
200
  }
164
201
  else if (flags & ts.TypeFlags.Undefined) {
165
202
  if (options.useUndefinedSymbolForUndefinedValue) {
166
- newNode = ts.factory.createIdentifier('undefined');
203
+ if (replaceOption === 'create') {
204
+ newNode = ts.factory.createIdentifier('undefined');
205
+ }
167
206
  newSource = 'undefined';
168
207
  }
169
208
  else {
170
- newNode = ts.factory.createParenthesizedExpression(ts.factory.createVoidZero());
209
+ if (replaceOption === 'create') {
210
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createVoidZero());
211
+ }
171
212
  newSource = '(void 0)';
172
213
  }
173
214
  }
@@ -176,19 +217,28 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
176
217
  }
177
218
  const originalSource = node.getText(sourceFile);
178
219
  const comment = ` ${originalSource.replace(/\/\*/g, ' *').replace(/\*\//g, '* ')} `;
179
- let result = ts.addSyntheticTrailingComment(newNode, ts.SyntaxKind.MultiLineCommentTrivia, comment);
220
+ let result = newNode
221
+ ? ts.addSyntheticTrailingComment(newNode, ts.SyntaxKind.MultiLineCommentTrivia, comment)
222
+ : undefined;
180
223
  newSource = `${newSource} /*${comment}*/`;
181
224
  if (/[\r\n]/m.test(originalSource)) {
182
- result = ts.factory.createParenthesizedExpression(result);
225
+ if (result) {
226
+ result = ts.factory.createParenthesizedExpression(result);
227
+ }
183
228
  newSource = `(${newSource})`;
184
229
  }
185
- ts.setTextRange(result, node);
186
- result[SYMBOL_ORIGINAL_NODE_DATA] = [
187
- originalSource,
188
- newSource,
189
- node.pos,
190
- node.end,
191
- ];
230
+ if (result) {
231
+ ts.setTextRange(result, node);
232
+ result[SYMBOL_ORIGINAL_NODE_DATA] = [
233
+ originalSource,
234
+ newSource,
235
+ node.pos,
236
+ node.end,
237
+ ];
238
+ }
239
+ if (replaceOption !== 'create') {
240
+ replaceOption(newSource, originalSource, node.pos, node.end, visitContext);
241
+ }
192
242
  return result;
193
243
  }
194
244
  catch {
@@ -494,3 +544,103 @@ function printNode(tsInstance, baseSource, sourceFile, node, posContext, origina
494
544
  }
495
545
  }
496
546
  }
547
+ ////////////////////////////////////////////////////////////////////////////////
548
+ export function transformAndPrintSource(sourceFile, program, context, options) {
549
+ return transformAndPrintSourceImpl(sourceFile, program, context, options)[0];
550
+ }
551
+ export function transformAndPrintSourceWithMap(sourceFile, program, context, originalSourceName, options, startOfSourceMap) {
552
+ const generator = new sourceMap.SourceMapGenerator(startOfSourceMap);
553
+ return transformAndPrintSourceImpl(sourceFile, program, context, options, originalSourceName, generator);
554
+ }
555
+ function transformAndPrintSourceImpl(sourceFile, program, context, options, originalSourceName, mapGenerator) {
556
+ const requiredOptions = assignDefaultValues(options);
557
+ if (mapGenerator) {
558
+ mapGenerator.setSourceContent(originalSourceName, sourceFile.getFullText());
559
+ }
560
+ const r = transformAndPrintNode({ pos: 0, diff: 0, lastLine: 0 }, sourceFile.getFullText(), sourceFile, program, context, requiredOptions, originalSourceName, mapGenerator);
561
+ return [r, mapGenerator?.toJSON()];
562
+ }
563
+ function transformAndPrintNode(posContext, baseSource, sourceFile, program, context, options, originalSourceName, mapGenerator) {
564
+ let output = '';
565
+ visitNodeChildren(sourceFile, sourceFile, sourceFile, program, options, context,
566
+ // fnVisit
567
+ (child, _parent, visitContext) => {
568
+ visitContext.lastChildPos = child.end;
569
+ return true;
570
+ },
571
+ // visitContext
572
+ {
573
+ headPrinted: false,
574
+ lastChildPos: 0,
575
+ },
576
+ // fnVisitBeforeReplace
577
+ (child, parent, visitContext) => {
578
+ if (!visitContext.headPrinted) {
579
+ visitContext.headPrinted = true;
580
+ if (child.pos > parent.pos) {
581
+ const text = baseSource.substring(parent.pos, child.pos);
582
+ output += text;
583
+ posContext.pos = child.pos;
584
+ }
585
+ }
586
+ else if (child.pos > visitContext.lastChildPos) {
587
+ const text = baseSource.substring(visitContext.lastChildPos, child.pos);
588
+ output += text;
589
+ posContext.pos = child.pos;
590
+ }
591
+ },
592
+ // fnVisitBeforeChild
593
+ () => {
594
+ return {
595
+ headPrinted: false,
596
+ lastChildPos: 0,
597
+ };
598
+ },
599
+ // fnVisitAfterChild
600
+ (node, _parent, childVisitContext) => {
601
+ if (!childVisitContext.headPrinted) {
602
+ output += baseSource.substring(node.pos, node.end);
603
+ posContext.pos = node.end;
604
+ }
605
+ else if (childVisitContext.lastChildPos < node.end) {
606
+ const text = baseSource.substring(childVisitContext.lastChildPos, node.end);
607
+ output += text;
608
+ posContext.pos = node.end;
609
+ }
610
+ },
611
+ // fnReplace
612
+ (newSource, originalSource, pos, end, visitContext) => {
613
+ const oldFull = baseSource.substring(pos, end);
614
+ const i = oldFull.lastIndexOf(originalSource);
615
+ const leadingUnchanged = i < 0 ? 0 : i;
616
+ const newText = i < 0
617
+ ? newSource
618
+ : oldFull.substring(0, i) +
619
+ newSource +
620
+ oldFull.substring(i + originalSource.length);
621
+ posContext.pos = pos + leadingUnchanged;
622
+ addMappingForCurrent(originalSource);
623
+ posContext.diff += newSource.length - originalSource.length;
624
+ posContext.pos += originalSource.length;
625
+ addMappingForCurrent();
626
+ posContext.pos = end;
627
+ output += newText;
628
+ visitContext.lastChildPos = end;
629
+ });
630
+ return output;
631
+ function addMappingForCurrent(name) {
632
+ const original = positionToLineAndColumn(sourceFile, posContext.pos, 0);
633
+ if (original.line !== posContext.lastLine) {
634
+ posContext.diff = 0;
635
+ posContext.lastLine = original.line;
636
+ }
637
+ if (mapGenerator) {
638
+ mapGenerator.addMapping({
639
+ original,
640
+ generated: positionToLineAndColumn(sourceFile, posContext.pos, posContext.diff),
641
+ source: originalSourceName,
642
+ name,
643
+ });
644
+ }
645
+ }
646
+ }
@@ -1,2 +1,2 @@
1
- declare const _default: "0.8.2";
1
+ declare const _default: "0.9.1";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.8.2';
1
+ export default '0.9.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },
@@ -112,7 +112,8 @@
112
112
  "ts-patch": "^3.3.0",
113
113
  "tslib": "^2.8.1",
114
114
  "typescript": "~5.5.4",
115
- "webpack": "^5.103.0"
115
+ "webpack": "^5.103.0",
116
+ "webpack-cli": "^6.0.1"
116
117
  },
117
118
  "peerDependencies": {
118
119
  "typescript": ">=5.5"